diff --git a/.benchmarks/README.md b/.benchmarks/README.md index eb062141cc7..75cf1018025 100644 --- a/.benchmarks/README.md +++ b/.benchmarks/README.md @@ -19,7 +19,7 @@ Things to take into account: - All benchmarks on a package will be shown on the same graph. - The value on `package` and `benchmarks` are regular expressions. - You have to explicitly add your new package here to make it appears on generated graphs. -- If you have benchmarks on the same package that takes much more time pero op than the rest, you should divide it into a separate graph for visibility. In this example we can see how we separated tests from the gnolang package into the ones finishing with `Equality` and `LoopyMain`, because `LoopyMain` is taking an order of magnitude more time per operation than the other tests: +- If you have benchmarks on the same package that takes much more time per op than the rest, you should divide it into a separate graph for visibility. In this example we can see how we separated tests from the gnolang package into the ones finishing with `Equality` and `LoopyMain`, because `LoopyMain` is taking an order of magnitude more time per operation than the other tests: ```yaml - name: Equality benchmarks (gnovm) benchmarks: [ '.Equality' ] @@ -31,4 +31,4 @@ Things to take into account: ## Add new checks for PRs -If we want to add a new package to check all the fast benchmarks on it on every PR, we should have a look into [gobenchdata-checks.yml](./gobenchdata-checks.yml). \ No newline at end of file +If we want to add a new package to check all the fast benchmarks on it on every PR, we should have a look into [gobenchdata-checks.yml](./gobenchdata-checks.yml). diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bcf4251abec..f13ce49ef45 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,7 +31,6 @@ /examples/gno.land/p/demo/seqid/ @thehowl /examples/gno.land/p/demo/ownable/ @leohhhn /examples/gno.land/p/demo/pausable/ @leohhhn -/examples/gno.land/p/demo/stack/ @harry-hov /examples/gno.land/p/demo/svg/ @moul /examples/gno.land/p/demo/tamagotchi/ @moul /examples/gno.land/p/demo/ui/ @moul @@ -64,11 +63,11 @@ /gnovm/ @jaekwon @moul @piux2 @thehowl /gnovm/stdlibs/ @thehowl /gnovm/tests/ @jaekwon @deelawn @thehowl @mvertes -/gnovm/cmd/gno/ @moul @thehowl @harry-hov +/gnovm/cmd/gno/ @moul @thehowl /gnovm/pkg/gnolang/ @jaekwon @moul @piux2 @deelawn /gnovm/pkg/doc/ @thehowl /gnovm/pkg/repl/ @mvertes @ajnavarro -/gnovm/pkg/gnomod/ @harry-hov +/gnovm/pkg/gnomod/ @thehowl /gnovm/pkg/gnoenv/ @gfanton /gnovm/pkg/transpiler/ @thehowl /gnovm/pkg/integration/ @gfanton @@ -91,5 +90,6 @@ /CONTRIBUTING.md @jaekwon @moul @gnolang/tech-staff /LICENSE.md @jaekwon /.github/ @moul @gnolang/tech-staff +/.github/workflows @ajnavarro @moul /.github/CODEOWNERS @jaekwon @moul /go.mod @gnolang/tech-staff # no unnecessary dependencies diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.md b/.github/ISSUE_TEMPLATE/BUG-REPORT.md index 6d0f2e573c3..70a20a4c47e 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.md @@ -12,9 +12,9 @@ Describe your issue in as much detail as possible here ### Your environment -* OS and version -* version of gno -* branch that causes this issue (with the commit hash) +* Go version (example: go1.22.4) +* OS and CPU architecture (example: linux/amd64) +* Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) ### Steps to reproduce diff --git a/.github/golangci.yml b/.github/golangci.yml index 8b38691ec75..e78d09a582e 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -38,6 +38,7 @@ linters: - gofumpt # Stricter gofmt - unused # Checks Go code for unused constants, variables, functions and types - gomodguard # Enforces an allow and block list for direct Go module dependencies + - forbidigo # Forbids some custom-set identifiers, like regexp.MatchString linters-settings: gofmt: @@ -60,6 +61,10 @@ linters-settings: - opinionated - performance - style + forbidigo: + forbid: + - p: '^regexp\.(Match|MatchString)$' + msg: it will re-compile the regexp for each execution; compile the regexp with regexp.Compile and store it as a singleton issues: whole-files: true diff --git a/.github/goreleaser-master.yaml b/.github/goreleaser-master.yaml new file mode 100644 index 00000000000..bca52615db8 --- /dev/null +++ b/.github/goreleaser-master.yaml @@ -0,0 +1,503 @@ +project_name: gno + +before: + hooks: + - go mod tidy + +builds: + - id: gno + main: ./gnovm/cmd/gno + binary: gno + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnoland + main: ./gno.land/cmd/gnoland + binary: gnoland + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnokey + main: ./gno.land/cmd/gnokey + binary: gnokey + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnoweb + main: ./gno.land/cmd/gnoweb + binary: gnoweb + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 +gomod: + proxy: true + +archives: + # https://goreleaser.com/customization/archive/ + - files: + # Standard Release Files + - LICENSE.md + - README.md + +signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + certificate: "${artifact}.pem" + args: + - sign-blob + - "--output-certificate=${certificate}" + - "--output-signature=${signature}" + - "${artifact}" + - "--yes" # needed on cosign 2.0.0+ + artifacts: checksum + output: true + +dockers: + # https://goreleaser.com/customization/docker/ + + # gno + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}:master-amd64" + build_flag_templates: + - "--target=gno" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}:master-arm64v8" + build_flag_templates: + - "--target=gno" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}:master-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 }}:master-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 + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-amd64" + build_flag_templates: + - "--target=gnoland" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-arm64v8" + build_flag_templates: + - "--target=gnoland" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-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:master-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 + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-amd64" + build_flag_templates: + - "--target=gnokey" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-arm64v8" + build_flag_templates: + - "--target=gnokey" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-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:master-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 + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-amd64" + build_flag_templates: + - "--target=gnoweb" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-arm64v8" + build_flag_templates: + - "--target=gnoweb" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-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:master-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 + +docker_manifests: + # https://goreleaser.com/customization/docker_manifest/ + + # gno + - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }} + 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 }}:master + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}:master-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}:master-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}:master-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}:master-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:master + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:master-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:master + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:master-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:master + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:master-armv7 + +docker_signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + artifacts: images + output: true + args: + - "sign" + - "${artifact}" + - "--yes" # needed on cosign 2.0.0+ + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + +source: + enabled: true + +sboms: + - artifacts: archive + - id: source # Two different sbom configurations need two different IDs + artifacts: source + +release: + draft: true + replace_existing_draft: true + prerelease: auto + make_latest: false + mode: append + footer: | + ### Container Images + + You can find all docker images at: + + https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }} + +nightly: + tag_name: master + publish_release: true + keep_single_release: true + name_template: "{{ incpatch .Version }}-{{ .ShortCommit }}-master" \ No newline at end of file diff --git a/.github/goreleaser-nightly.yaml b/.github/goreleaser-nightly.yaml new file mode 100644 index 00000000000..3dac915b7cd --- /dev/null +++ b/.github/goreleaser-nightly.yaml @@ -0,0 +1,502 @@ +project_name: gno + +before: + hooks: + - go mod tidy + +builds: + - id: gno + main: ./gnovm/cmd/gno + binary: gno + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnoland + main: ./gno.land/cmd/gnoland + binary: gnoland + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnokey + main: ./gno.land/cmd/gnokey + binary: gnokey + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnoweb + main: ./gno.land/cmd/gnoweb + binary: gnoweb + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 +gomod: + proxy: true + +archives: + # https://goreleaser.com/customization/archive/ + - files: + # Standard Release Files + - LICENSE.md + - README.md + +signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + certificate: "${artifact}.pem" + args: + - sign-blob + - "--output-certificate=${certificate}" + - "--output-signature=${signature}" + - "${artifact}" + - "--yes" # needed on cosign 2.0.0+ + artifacts: checksum + output: true + +dockers: + # https://goreleaser.com/customization/docker/ + + # gno + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}:nightly-amd64" + build_flag_templates: + - "--target=gno" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}:nightly-arm64v8" + build_flag_templates: + - "--target=gno" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}:nightly-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 }}:nightly-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 + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-amd64" + build_flag_templates: + - "--target=gnoland" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-arm64v8" + build_flag_templates: + - "--target=gnoland" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-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:nightly-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 + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-amd64" + build_flag_templates: + - "--target=gnokey" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-arm64v8" + build_flag_templates: + - "--target=gnokey" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-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:nightly-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 + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-amd64" + build_flag_templates: + - "--target=gnoweb" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-arm64v8" + build_flag_templates: + - "--target=gnoweb" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-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:nightly-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 + +docker_manifests: + # https://goreleaser.com/customization/docker_manifest/ + + # gno + - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }} + 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 }}:nightly + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}:nightly-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}:nightly-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}:nightly-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}:nightly-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:nightly + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:nightly-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:nightly + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:nightly-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:nightly + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:nightly-armv7 + +docker_signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + artifacts: images + output: true + args: + - "sign" + - "${artifact}" + - "--yes" # needed on cosign 2.0.0+ + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + +source: + enabled: true + +sboms: + - artifacts: archive + - id: source # Two different sbom configurations need two different IDs + artifacts: source + +release: + draft: true + replace_existing_draft: true + prerelease: auto + mode: append + footer: | + ### Container Images + + You can find all docker images at: + + https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }} + +nightly: + tag_name: nightly + publish_release: true + keep_single_release: true + name_template: "{{ incpatch .Version }}-{{ .ShortCommit }}-nightly" \ No newline at end of file diff --git a/.github/goreleaser.yaml b/.github/goreleaser.yaml new file mode 100644 index 00000000000..ab98aa92555 --- /dev/null +++ b/.github/goreleaser.yaml @@ -0,0 +1,496 @@ +project_name: gno + +before: + hooks: + - go mod tidy + +builds: + - id: gno + main: ./gnovm/cmd/gno + binary: gno + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnoland + main: ./gno.land/cmd/gnoland + binary: gnoland + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnokey + main: ./gno.land/cmd/gnokey + binary: gnokey + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + - id: gnoweb + main: ./gno.land/cmd/gnoweb + binary: gnoweb + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 +gomod: + proxy: true + +archives: + # https://goreleaser.com/customization/archive/ + - files: + # Standard Release Files + - LICENSE.md + - README.md + +signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + certificate: "${artifact}.pem" + args: + - sign-blob + - "--output-certificate=${certificate}" + - "--output-signature=${signature}" + - "${artifact}" + - "--yes" # needed on cosign 2.0.0+ + artifacts: checksum + output: true + +dockers: + # https://goreleaser.com/customization/docker/ + + # gno + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64" + build_flag_templates: + - "--target=gno" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8" + build_flag_templates: + - "--target=gno" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}:latest-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 }}:latest-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 + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-amd64" + build_flag_templates: + - "--target=gnoland" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-arm64v8" + build_flag_templates: + - "--target=gnoland" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-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:latest-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 + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-amd64" + build_flag_templates: + - "--target=gnokey" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-arm64v8" + build_flag_templates: + - "--target=gnokey" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-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:latest-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 + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-amd64" + build_flag_templates: + - "--target=gnoweb" + - "--platform=linux/amd64" + - "--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: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-arm64v8" + build_flag_templates: + - "--target=gnoweb" + - "--platform=linux/arm64/v8" + - "--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: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-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:latest-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 + +docker_manifests: + # https://goreleaser.com/customization/docker_manifest/ + + # gno + - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }} + 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 }}:latest + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}:latest-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}:latest-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:latest + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-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:latest + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-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:latest + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv7 + +docker_signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + artifacts: images + output: true + args: + - "sign" + - "${artifact}" + - "--yes" # needed on cosign 2.0.0+ + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + +source: + enabled: true + +sboms: + - artifacts: archive + - id: source # Two different sbom configurations need two different IDs + artifacts: source + +release: + draft: true + replace_existing_draft: true + prerelease: auto + mode: append + footer: | + ### Container Images + + You can find all docker images at: + + https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }} \ No newline at end of file diff --git a/.github/workflows/DELETEdocker.yml b/.github/workflows/DELETEdocker.yml deleted file mode 100644 index 374eab763c9..00000000000 --- a/.github/workflows/DELETEdocker.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: docker -on: - push: - branches: [ "master" ] - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - build-main: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Get commit SHA - id: commit - run: echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - if: (github.event_name != 'pull_request') - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: | - ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest - ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ steps.commit.outputs.sha }} - - build-slim: - runs-on: ubuntu-latest - strategy: - matrix: - target: [ gnoland-slim, gnokey-slim, gno-slim, gnofaucet-slim, gnoweb-slim ] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Get commit SHA - id: commit - run: echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - if: (github.event_name != 'pull_request') - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64,linux/arm64 - target: ${{ matrix.target }} - push: ${{ github.event_name != 'pull_request' }} - tags: | - ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/${{ matrix.target }}:latest - ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/${{ matrix.target }}:${{ steps.commit.outputs.sha }} diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index cb0eddcef19..06dfb4ab903 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -15,4 +15,4 @@ jobs: assign-author: runs-on: ubuntu-latest steps: - - uses: toshimaru/auto-author-assign@v2.1.0 + - uses: toshimaru/auto-author-assign@v2.1.1 diff --git a/.github/workflows/autocounterd.yml b/.github/workflows/autocounterd.yml index 45149af82fa..66aced0d89c 100644 --- a/.github/workflows/autocounterd.yml +++ b/.github/workflows/autocounterd.yml @@ -23,23 +23,23 @@ jobs: uses: actions/checkout@v4 - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker metadata autcounterd + - name: Docker metadata autocounterd id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/${{ github.repository }}/autcounterd + images: ghcr.io/${{ github.repository }}/autocounterd tags: | type=raw,value=latest type=semver,pattern=v{{version}} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: ./misc/autocounterd push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/build_template.yml b/.github/workflows/build_template.yml index a984589caaa..430aa393a73 100644 --- a/.github/workflows/build_template.yml +++ b/.github/workflows/build_template.yml @@ -16,10 +16,10 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} - + - name: Checkout code uses: actions/checkout@v4 - + - name: Check generated files are up to date working-directory: ${{ inputs.modulepath }} run: | diff --git a/.github/workflows/contribs.yml b/.github/workflows/contribs.yml index 8fdcce9332c..3739339f7be 100644 --- a/.github/workflows/contribs.yml +++ b/.github/workflows/contribs.yml @@ -5,10 +5,7 @@ on: branches: - master workflow_dispatch: - pull_request: - paths: - - "contribs/**" - - ".github/**" + pull_request: jobs: setup: @@ -18,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - id: set-matrix - run: echo "::set-output name=programs::$(ls -d contribs/*/ | cut -d/ -f2 | jq -R -s -c 'split("\n")[:-1]')" + run: echo "::set-output name=programs::$(ls -d contribs/*/ | cut -d/ -f2 | jq -R -s -c 'split("\n")[:-1]')" main: needs: setup strategy: @@ -30,4 +27,4 @@ jobs: with: modulepath: contribs/${{ matrix.program }} secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/dependabot-tidy.yml b/.github/workflows/dependabot-tidy.yml index 035e3246345..59e9e1c8146 100644 --- a/.github/workflows/dependabot-tidy.yml +++ b/.github/workflows/dependabot-tidy.yml @@ -28,7 +28,7 @@ jobs: run: | # Ensure Make is installed make --version - + # Run the tidy target make tidy diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 79680369822..d800147a498 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -10,7 +10,7 @@ jobs: trigger-netlify-docs-deploy: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: github-token: ${{ secrets.DOCS_DEPLOY_PAT }} script: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..262b341276c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,34 @@ +name: "docs / lint" + +on: + push: + paths: + - 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: '1.22' + + - 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 a01f465f38b..5b3c3c1fbf1 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -2,11 +2,6 @@ name: examples on: pull_request: - paths: - - "go.sum" - - "gnovm/**" - - "examples/**" - - ".github/workflows/examples.yml" push: branches: [ "master" ] @@ -20,7 +15,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" runs-on: ubuntu-latest timeout-minutes: 30 @@ -36,7 +30,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -60,7 +53,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -74,6 +66,22 @@ jobs: - run: make lint -C ./examples # TODO: consider running lint on every other directories, maybe in "warning" mode? # TODO: track coverage + fmt: + strategy: + fail-fast: false + matrix: + goversion: [ "1.22.x" ] + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.goversion }} + - run: | + make fmt -C ./examples + # Check if there are changes after running make fmt + git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) mod-tidy: strategy: fail-fast: false @@ -92,4 +100,4 @@ jobs: # Find all directories containing gno.mod file find ./examples -name "gno.mod" -execdir go run "$GNO_CMD" mod tidy \; # Check if there are changes after running gno mod tidy - git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) \ No newline at end of file + git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 9f1c11eb4ea..11f04ca8282 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -28,7 +28,7 @@ jobs: run: mv .github/.fossa.yml . - name: Cache Coursier cache - uses: coursier/cache-action@v6.4.5 + uses: coursier/cache-action@v6.4.6 - name: Set up JDK 17 uses: coursier/setup-action@v1.3.5 diff --git a/.github/workflows/gnofmt_template.yml b/.github/workflows/gnofmt_template.yml new file mode 100644 index 00000000000..1ba66d0fbe3 --- /dev/null +++ b/.github/workflows/gnofmt_template.yml @@ -0,0 +1,24 @@ +on: + workflow_call: + inputs: + path: + required: true + type: string + go-version: + required: true + type: string + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + - name: Checkout code + uses: actions/checkout@v4 + - name: Fmt + env: + GNOFMT_PATH: ${{ inputs.path }} + run: go run ./gnovm/cmd/gno fmt -v -diff $GNOFMT_PATH diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 97df3f13a2a..9451d6da3a1 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -5,10 +5,7 @@ on: branches: - master workflow_dispatch: - pull_request: - paths: - - "gno.land/**" - - ".github/**" + pull_request: jobs: main: @@ -17,4 +14,4 @@ jobs: with: modulepath: "gno.land" secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index ac304328dbb..7e7586b23d9 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -5,10 +5,7 @@ on: branches: - master workflow_dispatch: - pull_request: - paths: - - "gnovm/**" - - ".github/**" + pull_request: jobs: main: @@ -17,4 +14,10 @@ jobs: with: modulepath: "gnovm" secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} + fmt: + name: Run Gno Fmt + uses: ./.github/workflows/gnofmt_template.yml + with: + path: "gnovm/stdlibs/..." + go-version: "1.22.x" diff --git a/.github/workflows/lint_template.yml b/.github/workflows/lint_template.yml index c1330d0a3d0..098650c1df2 100644 --- a/.github/workflows/lint_template.yml +++ b/.github/workflows/lint_template.yml @@ -20,7 +20,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: working-directory: ${{ inputs.modulepath }} args: diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index 5fbdef73190..b824235ca2b 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -7,10 +7,7 @@ on: branches: - master workflow_dispatch: - pull_request: - paths: - - "misc/**" - - ".github/**" + pull_request: jobs: main: @@ -32,4 +29,4 @@ jobs: with: modulepath: misc/${{ matrix.program }} secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/monthly-snapshots.yml b/.github/workflows/monthly-snapshots.yml deleted file mode 100644 index 5bf912f503f..00000000000 --- a/.github/workflows/monthly-snapshots.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Monthly Snapshots - -on: - schedule: - - cron: '0 0 1 * *' - workflow_dispatch: - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Generate tag name - id: tag_name - run: echo "::set-output name=tag_name::v0.0.1-dev.$(date +'%Y.%m.%d')" - - name: Release - uses: softprops/action-gh-release@v2 - with: - generate_release_notes: true - prerelease: true - tag_name: '${{ steps.tag_name.outputs.tag_name }}' diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml new file mode 100644 index 00000000000..6e3e3e58935 --- /dev/null +++ b/.github/workflows/nightlies.yml @@ -0,0 +1,45 @@ +name: Trigger nightly build + +on: + schedule: + - cron: '0 0 * * 2-6' + workflow_dispatch: + +permissions: + contents: write # needed to write releases + id-token: write # needed for keyless signing + packages: write # needed for ghcr access + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + cache: true + + - uses: sigstore/cosign-installer@v3.5.0 + - uses: anchore/sbom-action/download-syft@v0.17.0 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: v1.26.2-pro + args: release --clean --nightly --config ./.github/goreleaser-nightly.yaml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index 6db5ffe4225..1c83a2854c2 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -3,8 +3,8 @@ name: portal-loop on: push: paths: - - misc/loop - - .github/workflows/portal-loop.yml + - "misc/loop/**" + - ".github/workflows/portal-loop.yml" branches: - "master" - "ops/portal-loop" @@ -39,7 +39,7 @@ jobs: type=semver,pattern=v{{version}} - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: ./misc/loop target: portalloopd diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml new file mode 100644 index 00000000000..3ed5353ec89 --- /dev/null +++ b/.github/workflows/releaser-master.yml @@ -0,0 +1,46 @@ +name: Trigger master build + +on: + push: + branches: + - "master" + workflow_dispatch: + +permissions: + contents: write # needed to write releases + id-token: write # needed for keyless signing + packages: write # needed for ghcr access + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + cache: true + + - uses: sigstore/cosign-installer@v3.5.0 + - uses: anchore/sbom-action/download-syft@v0.17.0 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: v1.26.2-pro + args: release --clean --nightly --config ./.github/goreleaser-master.yaml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml new file mode 100644 index 00000000000..9148d9ac15c --- /dev/null +++ b/.github/workflows/releaser.yml @@ -0,0 +1,45 @@ +name: Go Releaser + +on: + push: + tags: + - "v*" + +permissions: + contents: write # needed to write releases + id-token: write # needed for keyless signing + packages: write # needed for ghcr access + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + cache: true + + - uses: sigstore/cosign-installer@v3.5.0 + - uses: anchore/sbom-action/download-syft@v0.17.0 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: v1.26.2-pro + args: release --clean --config ./.github/goreleaser.yaml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 7471fd74cb7..57e84793c94 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -6,9 +6,6 @@ on: - master workflow_dispatch: pull_request: - paths: - - "tm2/**" - - ".github/**" jobs: main: diff --git a/.gitignore b/.gitignore index 0a06c2cf055..7d3f3f92b41 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ genesis.json # Build leftovers build +dist # Legacy .gitignore data/* @@ -29,3 +30,7 @@ pbbindings.go # Test coverage leftovers cover.out coverage.out + +*.swp +*.swo +*.bak diff --git a/.gitpod.yml b/.gitpod.yml index 794108f4613..e1d90d7c097 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -31,13 +31,13 @@ tasks: ports: - name: gnoweb - description: "the Gno.land web server" + description: "the gno.land web server" port: 8888 onOpen: open-preview - name: "gnoland RPC" description: "the RPC server, managed by tendermint2" - port: 36657 + port: 26657 onOpen: notify github: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e041ab18875..bc125a6da73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.21+) +- Go (version 1.22+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a18c7125a82..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -# build gno -FROM golang:1.22 AS build-gno -RUN mkdir -p /opt/gno/src /opt/build -WORKDIR /opt/build -ADD go.mod go.sum . -RUN go mod download -ADD . ./ -RUN go build -o ./build/gnoland ./gno.land/cmd/gnoland -RUN go build -o ./build/gnokey ./gno.land/cmd/gnokey -RUN go build -o ./build/gnoweb ./gno.land/cmd/gnoweb -RUN go build -o ./build/gno ./gnovm/cmd/gno -RUN ls -la ./build -ADD . /opt/gno/src/ -RUN rm -rf /opt/gno/src/.git - -# build faucet -FROM golang:1.22 AS build-faucet -RUN mkdir -p /opt/gno/src /opt/build -WORKDIR /opt/build -ADD contribs/gnofaucet/go.mod contribs/gnofaucet/go.sum . -RUN go mod download -ADD contribs/gnofaucet ./ -RUN go build -o ./build/gnofaucet . - - -# runtime-base + runtime-tls -FROM debian:stable-slim AS runtime-base -ENV PATH="${PATH}:/opt/gno/bin" \ - GNOROOT="/opt/gno/src" -WORKDIR /opt/gno/src -FROM runtime-base AS runtime-tls -RUN apt-get update && apt-get install -y expect ca-certificates && update-ca-certificates - -# slim images -FROM runtime-base AS gnoland-slim -WORKDIR /opt/gno/src/gno.land/ -COPY --from=build-gno /opt/build/build/gnoland /opt/gno/bin/ -ENTRYPOINT ["gnoland"] -EXPOSE 26657 36657 - -FROM runtime-base AS gnokey-slim -COPY --from=build-gno /opt/build/build/gnokey /opt/gno/bin/ -ENTRYPOINT ["gnokey"] - -FROM runtime-base AS gno-slim -COPY --from=build-gno /opt/build/build/gno /opt/gno/bin/ -ENTRYPOINT ["gno"] - -FROM runtime-tls AS gnofaucet-slim -COPY --from=build-faucet /opt/build/build/gnofaucet /opt/gno/bin/ -ENTRYPOINT ["gnofaucet"] -EXPOSE 5050 - -FROM runtime-tls AS gnoweb-slim -COPY --from=build-gno /opt/build/build/gnoweb /opt/gno/bin/ -COPY --from=build-gno /opt/gno/src/gno.land/cmd/gnoweb /opt/gno/src/gnoweb -ENTRYPOINT ["gnoweb"] -EXPOSE 8888 - -# all, contains everything. -FROM runtime-tls AS all -COPY --from=build-gno /opt/build/build/* /opt/gno/bin/ -COPY --from=build-faucet /opt/build/build/* /opt/gno/bin/ -COPY --from=build-gno /opt/gno/src /opt/gno/src -# gofmt is required by `gnokey maketx addpkg` -COPY --from=build-gno /usr/local/go/bin/gofmt /usr/bin diff --git a/Dockerfile.release b/Dockerfile.release new file mode 100644 index 00000000000..2e36453382e --- /dev/null +++ b/Dockerfile.release @@ -0,0 +1,48 @@ +FROM alpine AS base + +ENV GNOROOT="/gnoroot/" +WORKDIR /gnoroot/ +CMD [ "" ] + +# +## ghcr.io/gnolang/gno/gnoland +FROM base as gnoland + +COPY ./gnoland /usr/bin/gnoland +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 + +EXPOSE 26656 26657 + +ENTRYPOINT [ "/usr/bin/gnoland" ] + + +# +## ghcr.io/gnolang/gno/gnokey +FROM base as gnokey + +COPY ./gnokey /usr/bin/gnokey +ENTRYPOINT [ "/usr/bin/gnokey" ] + + +# +## ghcr.io/gnolang/gno/gnoweb +FROM base as gnoweb + +COPY ./gnoweb /usr/bin/gnoweb +EXPOSE 8888 +ENTRYPOINT [ "/usr/bin/gnoweb" ] + + +# +## ghcr.io/gnolang/gno +FROM base as gno + +COPY ./gno /usr/bin/gno +COPY ./examples /gnoroot/examples/ +COPY ./gnovm/stdlibs /gnoroot/gnovm/stdlibs/ +COPY ./gnovm/tests/stdlibs /gnoroot/gnovm/tests/stdlibs/ + +ENTRYPOINT [ "/usr/bin/gno" ] diff --git a/LICENSE.md b/LICENSE.md index b5d453a337e..be2cf7f5999 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -20,7 +20,7 @@ You should have received a copy of the GNO Network General Public License along with this program. If not, see . Attached below are the terms of the GNO Network General Public License, Version -4 (a fork of the GNU Afferro General Public License 3). +4 (a fork of the GNU Affero General Public License 3). ## Additional Terms @@ -409,7 +409,7 @@ that material) supplement the terms of this License with terms: - g) Requiring strong attribution such as notices on any user interfaces that run or convey any covered work, such as a prominent link to a URL on the header of a website, such that all users of the covered work may - become aware of the the notice, for a period no longer than 20 years. + become aware of the notice, for a period no longer than 20 years. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you diff --git a/Makefile b/Makefile index c2f5156e054..fe862d52893 100644 --- a/Makefile +++ b/Makefile @@ -35,16 +35,16 @@ install: install.gnokey install.gno install.gnodev .PHONY: install.gnokey install.gnokey: $(MAKE) --no-print-directory -C ./gno.land install.gnokey - # \033[0;32m ... \033[0m is ansi for green text. - @echo "\033[0;32m[+] 'gnokey' has been installed. Read more in ./gno.land/\033[0m" + @# \033[0;32m ... \033[0m is ansi for green text. + @printf "\033[0;32m[+] 'gnokey' has been installed. Read more in ./gno.land/\033[0m\n" .PHONY: install.gno install.gno: $(MAKE) --no-print-directory -C ./gnovm install - @echo "\033[0;32m[+] 'gno' has been installed. Read more in ./gnovm/\033[0m" + @printf "\033[0;32m[+] 'gno' has been installed. Read more in ./gnovm/\033[0m\n" .PHONY: install.gnodev install.gnodev: - $(MAKE) --no-print-directory -C ./contribs install.gnodev - @echo "\033[0;32m[+] 'gnodev' has been installed. Read more in ./contribs/gnodev/\033[0m" + $(MAKE) --no-print-directory -C ./contribs/gnodev install + @printf "\033[0;32m[+] 'gnodev' has been installed. Read more in ./contribs/gnodev/\033[0m\n" # old aliases .PHONY: install_gnokey diff --git a/README.md b/README.md index 9d343d51b57..19ac161e790 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Gno is an interpreted and fully-deterministic implementation of the Go programming language, designed to build succinct and composable smart contracts. -The first blockchain to use it is Gno.land, a +The first blockchain to use it is gno.land, a [Proof of Contribution](./docs/concepts/proof-of-contribution.md)-based chain, backed by a variation of the [Tendermint](https://docs.tendermint.com/v0.34/introduction/what-is-tendermint.html) consensus engine. @@ -47,7 +47,7 @@ repository offers more resources to dig into. We are eager to see your first PR!

-[Gno Playground](https://play.gno.land), available at [play.gno.land](https://play.gno.land), is a web app that allows users to write, share, and deploy Gno code. Developers can seamlessly test, debug, and deploy realms and packages on Gno.land, while being able to collaborate with peers to work on projects together and seek assistance. A key feature of Gno Playground is the ability to get started without the need to install any tools or manage any services, offering immediate access and convenience for users. +[Gno Playground](https://play.gno.land), available at [play.gno.land](https://play.gno.land), is a web app that allows users to write, share, and deploy Gno code. Developers can seamlessly test, debug, and deploy realms and packages on gno.land, while being able to collaborate with peers to work on projects together and seek assistance. A key feature of Gno Playground is the ability to get started without the need to install any tools or manage any services, offering immediate access and convenience for users. ## Repository structure diff --git a/contribs/Makefile b/contribs/Makefile index bfd1fcb9889..e743351bcfd 100644 --- a/contribs/Makefile +++ b/contribs/Makefile @@ -3,6 +3,8 @@ help: @echo "Available make commands:" @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' +programs=$(wildcard */) + # command to run dependency utilities, like goimports. rundep=go run -modfile ../misc/devdeps/go.mod @@ -25,15 +27,31 @@ GOTEST_FLAGS ?= -v -p 1 -timeout=30m ######################################## # Dev tools .PHONY: install -install: install.gnomd install.gnodev install.gnofaucet +install: + @echo 'To install a tool, go to the subdirectory, then run `make install`.' + @echo 'To do a full installation, run `make install_all`.' + +install_all: $(addprefix install.,$(programs)) +install.%: + @echo "[+] make -C $(subst install.,,$@) install" + $(MAKE) --no-print-directory -C $(subst install.,,$@) install +.PHONY: install_all -install.gnomd:; cd gnomd && go install . -install.gnodev:; $(MAKE) -C ./gnodev install -install.gnofaucet:; $(MAKE) -C ./gnofaucet install +######################################## +# Test suite +test: $(addprefix test.,$(programs)) +test.%: + @echo "[+] make -C $(subst test.,,$@) install" + $(MAKE) --no-print-directory -C $(subst test.,,$@) test +.PHONY: test -.PHONY: clean -clean: - rm -rf build +######################################## +# Lint +.PHONY: lint +lint: $(addprefix lint.,$(programs)) +lint.%: + @echo "[+] make -C $(subst lint.,,$@) install" + $(MAKE) --no-print-directory -C $(subst lint.,,$@) lint ######################################## # Dev tools @@ -45,23 +63,4 @@ fmt: .PHONY: tidy tidy: - @for gomod in `find . -name go.mod`; do ( \ - dir=`dirname $$gomod`; \ - set -xe; \ - cd $$dir; \ - go mod tidy -v; \ - ); done - -######################################## -# Test suite -.PHONY: test -test: test.gnodev -test.gnodev: - $(MAKE) -C ./gnodev test - -######################################## -# Lint -.PHONY: test -lint: lint.gnodev -lint.gnodev: - $(MAKE) -C ./gnodev test + find . -name go.mod -execdir go mod tidy -v \; diff --git a/contribs/gnodev/README.md b/contribs/gnodev/README.md index fca018c83bf..6da9e7b1ebc 100644 --- a/contribs/gnodev/README.md +++ b/contribs/gnodev/README.md @@ -2,6 +2,8 @@ `gnodev` is designed to be a robust and user-friendly tool in your realm package development journey, streamlining your workflow and enhancing productivity. +We will only give a quick overview below. You may find the official documentation at [docs/gno-tooling/gnodev.md](../../docs/gno-tooling/cli/gnodev.md). + ### Synopsis **gnodev** [**-minimal**] [**-no-watch**] [**PKG_PATH ...**] diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go index 9b769321c83..2c694b608bb 100644 --- a/contribs/gnodev/cmd/gnodev/main.go +++ b/contribs/gnodev/cmd/gnodev/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "flag" "fmt" "log/slog" @@ -30,6 +31,8 @@ const ( 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) @@ -38,7 +41,6 @@ var ( type devCfg struct { // Listeners - webListenerAddr string nodeRPCListenerAddr string nodeP2PListenerAddr string nodeProxyAppListenerAddr string @@ -48,7 +50,15 @@ type devCfg struct { home string root string premineAccounts varPremineAccounts - balancesFile string + + // Files + balancesFile string + genesisFile string + txsFile string + + // Web Configuration + webListenerAddr string + webRemoteHelperAddr string // Node Configuration minimal bool @@ -58,13 +68,14 @@ type devCfg struct { maxGas int64 chainId string serverMode bool + unsafeAPI bool } var defaultDevOptions = &devCfg{ chainId: "dev", maxGas: 10_000_000_000, webListenerAddr: "127.0.0.1:8888", - nodeRPCListenerAddr: "127.0.0.1:36657", + nodeRPCListenerAddr: "127.0.0.1:26657", deployKey: DefaultDeployerAddress.String(), home: gnoenv.HomeDir(), root: gnoenv.RootDir(), @@ -84,9 +95,7 @@ func main() { 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.`, + 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 { @@ -115,7 +124,14 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { &c.webListenerAddr, "web-listener", defaultDevOptions.webListenerAddr, - "web server listening address", + "web server listener address", + ) + + fs.StringVar( + &c.webRemoteHelperAddr, + "web-help-remote", + defaultDevOptions.webRemoteHelperAddr, + "web server help page's remote addr (default to )", ) fs.StringVar( @@ -138,6 +154,20 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { "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", @@ -193,12 +223,31 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { 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", + ) +} + +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 { @@ -238,7 +287,8 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) { // Setup Dev Node // XXX: find a good way to export or display node logs nodeLogger := logger.WithGroup(NodeLogName) - devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, balances, pkgpaths) + nodeCfg := setupDevNodeConfig(cfg, logger, emitterServer, balances, pkgpaths) + devNode, err := setupDevNode(ctx, cfg, nodeCfg) if err != nil { return err } @@ -258,6 +308,23 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) { // Setup gnoweb webhandler := setupGnoWebServer(logger.WithGroup(WebLogName), cfg, devNode) + // 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) @@ -293,12 +360,18 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) { return runEventLoop(ctx, logger, book, rt, devNode, watcher) } -var helper string = ` -A Accounts - Display known accounts and balances -H Help - Display this message -R Reload - Reload all packages to take change into account. -Ctrl+R Reset - Reset application state. -Ctrl+C Exit - Exit the application +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( @@ -309,6 +382,20 @@ func runEventLoop( 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) + } + + defer func() { + if exported == 0 { + _ = os.RemoveAll(path) + } + }() + keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt) for { var err error @@ -344,8 +431,10 @@ func runEventLoop( 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 { @@ -360,8 +449,48 @@ func runEventLoop( 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: } diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go index c79ab9d18bf..578cf525751 100644 --- a/contribs/gnodev/cmd/gnodev/setup_node.go +++ b/contribs/gnodev/cmd/gnodev/setup_node.go @@ -10,24 +10,51 @@ import ( gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" "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, - logger *slog.Logger, - cfg *devCfg, - remitter emitter.Emitter, - balances gnoland.Balances, - pkgspath []gnodev.PackagePath, + devCfg *devCfg, + nodeConfig *gnodev.NodeConfig, ) (*gnodev.Node, error) { - config := setupDevNodeConfig(cfg, balances, pkgspath) - return gnodev.NewDevNode(ctx, logger, remitter, config) + logger := nodeConfig.Logger + + if devCfg.txsFile != "" { // Load txs files + var err error + nodeConfig.InitialTxs, err = parseTxs(devCfg.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) + if err != nil { + return nil, fmt.Errorf("unable to load genesis file %q: %w", devCfg.genesisFile, err) + } + + // Override balances and txs + nodeConfig.BalancesList = state.Balances + nodeConfig.InitialTxs = state.Txs + + logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(nodeConfig.InitialTxs)) + } + + return gnodev.NewDevNode(ctx, nodeConfig) } // setupDevNodeConfig creates and returns a new dev.NodeConfig. -func setupDevNodeConfig(cfg *devCfg, balances gnoland.Balances, pkgspath []gnodev.PackagePath) *gnodev.NodeConfig { +func setupDevNodeConfig( + cfg *devCfg, + logger *slog.Logger, + emitter emitter.Emitter, + balances gnoland.Balances, + pkgspath []gnodev.PackagePath, +) *gnodev.NodeConfig { config := gnodev.DefaultNodeConfig(cfg.root) + + config.Logger = logger + config.Emitter = emitter config.BalancesList = balances.List() config.PackagesPathList = pkgspath config.TMConfig.RPC.ListenAddress = resolveUnixOrTCPAddr(cfg.nodeRPCListenerAddr) @@ -42,6 +69,20 @@ func setupDevNodeConfig(cfg *devCfg, balances gnoland.Balances, pkgspath []gnode return config } +func extractAppStateFromGenesisFile(path string) (*gnoland.GnoGenesisState, error) { + doc, err := types.GenesisDocFromFile(path) + if err != nil { + return nil, fmt.Errorf("unable to parse doc file: %w", err) + } + + state, ok := doc.AppState.(gnoland.GnoGenesisState) + if !ok { + return nil, fmt.Errorf("invalid `GnoGenesisState` app state") + } + + return &state, nil +} + func resolveUnixOrTCPAddr(in string) (out string) { var err error var addr net.Addr diff --git a/contribs/gnodev/cmd/gnodev/setup_web.go b/contribs/gnodev/cmd/gnodev/setup_web.go index 17df502c3d8..635c27af19d 100644 --- a/contribs/gnodev/cmd/gnodev/setup_web.go +++ b/contribs/gnodev/cmd/gnodev/setup_web.go @@ -11,9 +11,15 @@ import ( // setupGnowebServer initializes and starts the Gnoweb server. func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) http.Handler { webConfig := gnoweb.NewDefaultConfig() - webConfig.RemoteAddr = dnode.GetRemoteAddress() - webConfig.HelpRemote = dnode.GetRemoteAddress() + webConfig.HelpChainID = cfg.chainId + webConfig.RemoteAddr = dnode.GetRemoteAddress() + webConfig.HelpRemote = cfg.webRemoteHelperAddr + + // If `HelpRemote` is empty default it to `RemoteAddr` + if webConfig.HelpRemote == "" { + webConfig.HelpRemote = webConfig.RemoteAddr + } app := gnoweb.MakeApp(logger, webConfig) return app.Router diff --git a/contribs/gnodev/cmd/gnodev/txs.go b/contribs/gnodev/cmd/gnodev/txs.go new file mode 100644 index 00000000000..0be33b68702 --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/txs.go @@ -0,0 +1,23 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/gnolang/gno/tm2/pkg/std" +) + +func parseTxs(txFile string) ([]std.Tx, error) { + if txFile == "" { + return nil, nil + } + + file, loadErr := os.Open(txFile) + if loadErr != nil { + return nil, fmt.Errorf("unable to open tx file %s: %w", txFile, loadErr) + } + defer file.Close() + + return std.ParseTxs(context.Background(), file) +} diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 4741a5d7326..77631a0190f 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,6 +1,8 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.21 +go 1.22 + +toolchain go1.22.4 replace github.com/gnolang/gno => ../.. @@ -13,7 +15,7 @@ require ( github.com/muesli/termenv v0.15.2 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 - golang.org/x/term v0.18.0 + golang.org/x/term v0.22.0 ) require ( @@ -28,15 +30,16 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/gotuna/gotuna v0.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // 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.18 // indirect @@ -46,33 +49,34 @@ require ( 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/rivo/uniseg v0.4.3 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.10.1 // indirect + github.com/rs/cors v1.11.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.9 // indirect - go.opentelemetry.io/otel v1.25.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.25.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.19.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index a2c9f154560..f4eb9423c21 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -60,8 +60,8 @@ github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -82,6 +82,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -95,8 +97,8 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/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= @@ -144,12 +146,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= -github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +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/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -164,20 +166,22 @@ 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.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -189,22 +193,22 @@ go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.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= @@ -215,37 +219,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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/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 v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnodev/internal/mock/server_emitter.go b/contribs/gnodev/internal/mock/server_emitter.go index d093d8855fe..eaf8d07210b 100644 --- a/contribs/gnodev/internal/mock/server_emitter.go +++ b/contribs/gnodev/internal/mock/server_emitter.go @@ -7,6 +7,12 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/events" ) +// Define empty event for NextEvent empty queue +var ( + eventNull = events.Custom("NULL") + EvtNull = eventNull.Type() +) + // ServerEmitter is an `emitter.Emitter` var _ emitter.Emitter = (*ServerEmitter)(nil) @@ -23,6 +29,8 @@ func (m *ServerEmitter) Emit(evt events.Event) { } func (m *ServerEmitter) NextEvent() (evt events.Event) { + evt = eventNull + m.muEvents.Lock() defer m.muEvents.Unlock() diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 4139c274b82..7f0c266bf48 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -29,9 +29,12 @@ import ( ) type NodeConfig struct { + Logger *slog.Logger DefaultDeployer crypto.Address BalancesList []gnoland.Balance PackagesPathList []PackagePath + Emitter emitter.Emitter + InitialTxs []std.Tx TMConfig *tmcfg.Config SkipFailingGenesisTxs bool NoReplay bool @@ -42,6 +45,8 @@ type NodeConfig struct { func DefaultNodeConfig(rootdir string) *NodeConfig { tmc := gnoland.NewDefaultTMConfig(rootdir) tmc.Consensus.SkipTimeoutCommit = false // avoid time drifting, see issue #1507 + tmc.Consensus.WALDisabled = true + tmc.Consensus.CreateEmptyBlocks = false defaultDeployer := crypto.MustAddressFromString(integration.DefaultAccount_Address) balances := []gnoland.Balance{ @@ -52,10 +57,11 @@ func DefaultNodeConfig(rootdir string) *NodeConfig { } return &NodeConfig{ + Logger: log.NewNoopLogger(), + Emitter: &emitter.NoopServer{}, DefaultDeployer: defaultDeployer, BalancesList: balances, ChainID: tmc.ChainID(), - PackagesPathList: []PackagePath{}, TMConfig: tmc, SkipFailingGenesisTxs: true, MaxGasPerBlock: 10_000_000_000, @@ -75,11 +81,15 @@ type Node struct { // keep track of number of loaded package to be able to skip them on restore loadedPackages int + + // state + initialState, state []std.Tx + currentStateIndex int } var DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) -func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitter, cfg *NodeConfig) (*Node, error) { +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) @@ -89,21 +99,24 @@ func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitte if err != nil { return nil, fmt.Errorf("unable to load genesis packages: %w", err) } - logger.Info("pkgs loaded", "path", cfg.PackagesPathList) + cfg.Logger.Info("pkgs loaded", "path", cfg.PackagesPathList) devnode := &Node{ - config: cfg, - client: client.NewLocal(), - emitter: emitter, - pkgs: mpkgs, - logger: logger, - loadedPackages: len(pkgsTxs), + config: cfg, + client: client.NewLocal(), + emitter: cfg.Emitter, + pkgs: mpkgs, + logger: cfg.Logger, + loadedPackages: len(pkgsTxs), + state: cfg.InitialTxs, + initialState: cfg.InitialTxs, + currentStateIndex: len(cfg.InitialTxs), } // generate genesis state genesis := gnoland.GnoGenesisState{ Balances: cfg.BalancesList, - Txs: pkgsTxs, + Txs: append(pkgsTxs, cfg.InitialTxs...), } if err := devnode.rebuildNode(ctx, genesis); err != nil { @@ -233,7 +246,7 @@ func (n *Node) updatePackages(paths ...string) error { pkgsUpdated += len(pkgslist) } - n.logger.Info(fmt.Sprintf("updated %d pacakges", pkgsUpdated)) + n.logger.Info(fmt.Sprintf("updated %d packages", pkgsUpdated)) return nil } @@ -249,11 +262,13 @@ func (n *Node) Reset(ctx context.Context) error { } // Generate a new genesis state based on the current packages - txs, err := n.pkgs.Load(DefaultFee) + pkgsTxs, err := n.pkgs.Load(DefaultFee) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } + // Append initialTxs + txs := append(pkgsTxs, n.initialState...) genesis := gnoland.GnoGenesisState{ Balances: n.config.BalancesList, Txs: txs, @@ -265,6 +280,8 @@ func (n *Node) Reset(ctx context.Context) error { return fmt.Errorf("unable to initialize a new node: %w", err) } + n.loadedPackages = len(pkgsTxs) + n.currentStateIndex = len(n.initialState) n.emitter.Emit(&events.Reset{}) return nil } @@ -408,7 +425,42 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error { return nil } +func (n *Node) handleEventTX(evt tm2events.Event) { + switch data := evt.(type) { + case bft.EventTx: + go func() { + // Use a separate goroutine in order to avoid a deadlock situation. + // This is needed because this callback may get called during node rebuilding while + // lock is held. + n.muNode.Lock() + defer n.muNode.Unlock() + + heigh := n.BlockStore().Height() + n.currentStateIndex++ + n.state = nil // invalidate state + + n.logger.Info("node state", "index", n.currentStateIndex, "height", heigh) + }() + + resEvt := events.TxResult{ + Height: data.Result.Height, + Index: data.Result.Index, + // XXX: Update this to split error for stack + Response: data.Result.Response, + } + + if err := amino.Unmarshal(data.Result.Tx, &resEvt.Tx); err != nil { + n.logger.Error("unable to unwrap tx result", + "error", err) + } + + n.emitter.Emit(resEvt) + } +} + func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState) (err error) { + noopLogger := log.NewNoopLogger() + // Stop the node if it's currently running. if err := n.stopIfRunning(); err != nil { return fmt.Errorf("unable to stop the node: %w", err) @@ -436,9 +488,16 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState) // Execute node creation and handle any errors. defer recoverFromError() - node, nodeErr := buildNode(n.logger, n.emitter, nodeConfig) - if nodeErr != nil { // Then for any node error - return fmt.Errorf("unable to build the node: %w", nodeErr) + // XXX: Redirect the node log somewhere else + node, nodeErr := gnoland.NewInMemoryNode(noopLogger, nodeConfig) + if nodeErr != nil { + return fmt.Errorf("unable to create a new node: %w", err) + } + + node.EventSwitch().AddListener("dev-emitter", n.handleEventTX) + + if startErr := node.Start(); startErr != nil { + return fmt.Errorf("unable to start the node: %w", startErr) } // Wait for the node to be ready @@ -485,45 +544,10 @@ func (n *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { return } -var noopLogger = log.NewNoopLogger() - -func buildNode(logger *slog.Logger, emitter emitter.Emitter, cfg *gnoland.InMemoryNodeConfig) (*node.Node, error) { - // XXX(TODO): Redirect the node log somewhere else - node, err := gnoland.NewInMemoryNode(noopLogger, cfg) - if err != nil { - return nil, fmt.Errorf("unable to create a new node: %w", err) - } - - node.EventSwitch().AddListener("dev-emitter", func(evt tm2events.Event) { - switch data := evt.(type) { - case bft.EventTx: - resEvt := events.TxResult{ - Height: data.Result.Height, - Index: data.Result.Index, - // XXX: Update this to split error for stack - Response: data.Result.Response, - } - - if err := amino.Unmarshal(data.Result.Tx, &resEvt.Tx); err != nil { - logger.Error("unable to unwarp tx result", - "error", err) - } - - emitter.Emit(resEvt) - } - }) - - if startErr := node.Start(); startErr != nil { - return nil, fmt.Errorf("unable to start the node: %w", startErr) - } - - return node, nil -} - func newNodeConfig(tmc *tmcfg.Config, chainid string, appstate gnoland.GnoGenesisState) *gnoland.InMemoryNodeConfig { // Create Mocked Identity pv := gnoland.NewMockedPrivValidator() - genesis := gnoland.NewDefaultGenesisConfig(pv.GetPubKey(), chainid) + genesis := gnoland.NewDefaultGenesisConfig(chainid) genesis.AppState = appstate // Add self as validator diff --git a/contribs/gnodev/pkg/dev/node_state.go b/contribs/gnodev/pkg/dev/node_state.go new file mode 100644 index 00000000000..846c4857784 --- /dev/null +++ b/contribs/gnodev/pkg/dev/node_state.go @@ -0,0 +1,142 @@ +package dev + +import ( + "context" + "errors" + "fmt" + + "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/gno.land/pkg/gnoland" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ErrEmptyState = errors.New("empty state") + +// Save the current state as initialState +func (n *Node) SaveCurrentState(ctx context.Context) error { + n.muNode.RLock() + defer n.muNode.RUnlock() + + // Get current blockstore state + state, err := n.getState(ctx) + if err != nil { + return fmt.Errorf("unable to save state: %s", err.Error()) + } + + n.initialState = state[:n.currentStateIndex] + return nil +} + +// Export the current state as list of txs +func (n *Node) ExportCurrentState(ctx context.Context) ([]std.Tx, error) { + n.muNode.RLock() + defer n.muNode.RUnlock() + + // Get current blockstore state + state, err := n.getState(ctx) + if err != nil { + return nil, fmt.Errorf("unable to save state: %s", err.Error()) + } + + return state[:n.currentStateIndex], nil +} + +func (n *Node) getState(ctx context.Context) ([]std.Tx, error) { + if n.state == nil { + var err error + n.state, err = n.getBlockStoreState(ctx) + if err != nil { + return nil, fmt.Errorf("unable to save state: %s", err.Error()) + } + } + + return n.state, nil +} + +// MoveBy adjusts the current state of the node by `x` transactions. +// `x` can be negative to move backward or positive to move forward, however, index boundaries are respected +// with a lower limit of 0 and upper limit equaling the total number of states. +// If a move is successful, node is reloaded. +func (n *Node) MoveBy(ctx context.Context, x int) error { + n.muNode.Lock() + defer n.muNode.Unlock() + + newIndex := n.currentStateIndex + x + state, err := n.getState(ctx) + if err != nil { + return fmt.Errorf("unable to get current state: %w", err) + } + + maxState := len(state) + switch { + case maxState == 0: // no state + return ErrEmptyState + case newIndex < 0: + newIndex = 0 + n.logger.Info("minimum state reached", "tx-index", fmt.Sprintf("%d/%d", newIndex, maxState)) + case newIndex > maxState: + newIndex = maxState + n.logger.Info("maximum state reached", "tx-index", fmt.Sprintf("%d/%d", newIndex, maxState)) + } + + if newIndex == n.currentStateIndex { + return nil + } + + // Load genesis packages + pkgsTxs, err := n.pkgs.Load(DefaultFee) + if err != nil { + return fmt.Errorf("unable to load pkgs: %w", err) + } + + newState := n.state[:newIndex] + + // Create genesis with loaded pkgs + previous state + genesis := gnoland.GnoGenesisState{ + Balances: n.config.BalancesList, + Txs: append(pkgsTxs, newState...), + } + + // Reset the node with the new genesis state. + if err = n.rebuildNode(ctx, genesis); err != nil { + return fmt.Errorf("uanble to rebuild node: %w", err) + } + + n.logger.Info("moving to", "tx-index", fmt.Sprintf("%d/%d", newIndex, maxState)) + + // Update node infos + n.currentStateIndex = newIndex + n.emitter.Emit(&events.Reload{}) + + return nil +} + +func (n *Node) MoveToPreviousTX(ctx context.Context) error { + return n.MoveBy(ctx, -1) +} + +func (n *Node) MoveToNextTX(ctx context.Context) error { + return n.MoveBy(ctx, 1) +} + +// Export the current state as genesis doc +func (n *Node) ExportStateAsGenesis(ctx context.Context) (*bft.GenesisDoc, error) { + n.muNode.RLock() + defer n.muNode.RUnlock() + + // Get current blockstore state + state, err := n.getState(ctx) + if err != nil { + return nil, fmt.Errorf("unable to save state: %s", err.Error()) + } + + // Get current blockstore state + doc := *n.Node.GenesisDoc() // copy doc + doc.AppState = gnoland.GnoGenesisState{ + Balances: n.config.BalancesList, + Txs: state, + } + + return &doc, nil +} diff --git a/contribs/gnodev/pkg/dev/node_state_test.go b/contribs/gnodev/pkg/dev/node_state_test.go new file mode 100644 index 00000000000..17f96367512 --- /dev/null +++ b/contribs/gnodev/pkg/dev/node_state_test.go @@ -0,0 +1,198 @@ +package dev + +import ( + "context" + "strconv" + "testing" + "time" + + emitter "github.com/gnolang/gno/contribs/gnodev/internal/mock" + "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testCounterRealm = "gno.land/r/dev/counter" + +func TestNodeMovePreviousTX(t *testing.T) { + const callInc = 5 + + node, emitter := testingCounterRealm(t, callInc) + + t.Run("Prev TX", func(t *testing.T) { + ctx := testingContext(t) + err := node.MoveToPreviousTX(ctx) + require.NoError(t, err) + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + + // Check for correct render update + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "4") + }) + + t.Run("Next TX", func(t *testing.T) { + ctx := testingContext(t) + err := node.MoveToNextTX(ctx) + require.NoError(t, err) + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + + // Check for correct render update + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "5") + }) + + t.Run("Multi Move TX", func(t *testing.T) { + ctx := testingContext(t) + moves := []struct { + Move int + ExpectedResult string + }{ + {-2, "3"}, + {2, "5"}, + {-5, "0"}, + {5, "5"}, + {-100, "0"}, + {100, "5"}, + {0, "5"}, + } + + t.Logf("initial state %d", callInc) + for _, tc := range moves { + t.Logf("moving from `%d`", tc.Move) + err := node.MoveBy(ctx, tc.Move) + require.NoError(t, err) + if tc.Move != 0 { + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + } + + // Check for correct render update + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, tc.ExpectedResult) + } + }) +} + +func TestSaveCurrentState(t *testing.T) { + ctx := testingContext(t) + + node, emitter := testingCounterRealm(t, 2) + + // Save current state + err := node.SaveCurrentState(ctx) + require.NoError(t, err) + + // Send a new tx + msg := gnoclient.MsgCall{ + PkgPath: testCounterRealm, + FuncName: "Inc", + Args: []string{"10"}, + } + + 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, events.EvtTxResult, emitter.NextEvent().Type()) + + // Test render + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "12") // 2 + 10 + + // Reset state + err = node.Reset(ctx) + require.NoError(t, err) + assert.Equal(t, events.EvtReset, emitter.NextEvent().Type()) + + render, err = testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "2") // Back to the original state +} + +func TestExportState(t *testing.T) { + node, _ := testingCounterRealm(t, 3) + + t.Run("export state", func(t *testing.T) { + ctx := testingContext(t) + state, err := node.ExportCurrentState(ctx) + require.NoError(t, err) + assert.Equal(t, 3, len(state)) + }) + + t.Run("export genesis doc", func(t *testing.T) { + ctx := testingContext(t) + doc, err := node.ExportStateAsGenesis(ctx) + require.NoError(t, err) + require.NotNil(t, doc.AppState) + + state, ok := doc.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + assert.Equal(t, 3, len(state.Txs)) + }) +} + +func testingCounterRealm(t *testing.T, inc int) (*Node, *emitter.ServerEmitter) { + t.Helper() + + const ( + // foo package + counterGnoMod = "module gno.land/r/dev/counter\n" + 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) + + // Call NewDevNode with no package should work + node, emitter := newTestingDevNode(t, counterPkg) + assert.Len(t, node.ListPkgs(), 1) + + // Test rendering + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "0") + + // Increment the counter 10 times + for i := 0; i < inc; i++ { + t.Logf("call %d", i) + // Craft `Inc` msg + msg := gnoclient.MsgCall{ + PkgPath: testCounterRealm, + FuncName: "Inc", + Args: []string{"1"}, + } + + 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, events.EvtTxResult, emitter.NextEvent().Type()) + } + + render, err = testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, strconv.Itoa(inc)) + + return node, emitter +} + +func testingContext(t *testing.T) context.Context { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*7) + t.Cleanup(cancel) + return ctx +} diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index d4868a5766f..afc27d769a7 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -8,7 +8,6 @@ import ( mock "github.com/gnolang/gno/contribs/gnodev/internal/mock" - "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" "github.com/gnolang/gno/contribs/gnodev/pkg/events" "github.com/gnolang/gno/gno.land/pkg/gnoclient" "github.com/gnolang/gno/gno.land/pkg/integration" @@ -34,7 +33,8 @@ func TestNewNode_NoPackages(t *testing.T) { // Call NewDevNode with no package should work cfg := DefaultNodeConfig(gnoenv.RootDir()) - node, err := NewDevNode(ctx, logger, &emitter.NoopServer{}, cfg) + cfg.Logger = logger + node, err := NewDevNode(ctx, cfg) require.NoError(t, err) assert.Len(t, node.ListPkgs(), 0) @@ -62,7 +62,8 @@ func Render(_ string) string { return "foo" } // Call NewDevNode with no package should work cfg := DefaultNodeConfig(gnoenv.RootDir()) cfg.PackagesPathList = []PackagePath{pkgpath} - node, err := NewDevNode(ctx, logger, &emitter.NoopServer{}, cfg) + cfg.Logger = logger + node, err := NewDevNode(ctx, cfg) require.NoError(t, err) assert.Len(t, node.ListPkgs(), 1) @@ -154,14 +155,14 @@ func Render(_ string) string { return "bar" } require.NoError(t, err) // Check reload event - assert.Equal(t, emitter.NextEvent().Type(), events.EvtReload) + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) // After a reload, render should succeed render, err = testingRenderRealm(t, node, "gno.land/r/dev/foobar") require.NoError(t, err) require.Equal(t, render, "bar") - assert.Nil(t, emitter.NextEvent()) + assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type()) } func TestNodeReset(t *testing.T) { @@ -215,7 +216,7 @@ func Render(_ string) string { return str } require.NoError(t, err) require.Equal(t, render, "foo") - assert.Nil(t, emitter.NextEvent()) + assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type()) } func testingRenderRealm(t *testing.T, node *Node, rlmpath string) (string, error) { @@ -244,9 +245,17 @@ func testingCallRealm(t *testing.T, node *Node, msgs ...gnoclient.MsgCall) (*cor RPCClient: node.Client(), } + signerInfo, err := signer.Info() + require.NoError(t, err) + + acc, _, err := cli.QueryAccount(signerInfo.GetAddress()) + require.NoError(t, err) + txcfg := gnoclient.BaseTxCfg{ - GasFee: "1000000ugnot", // Gas fee - GasWanted: 2_000_000, // Gas wanted + GasFee: "1000000ugnot", // Gas fee + GasWanted: 2_000_000, // Gas wanted + AccountNumber: acc.AccountNumber, + SequenceNumber: acc.Sequence, } return cli.Call(txcfg, msgs...) @@ -286,7 +295,9 @@ func newTestingDevNode(t *testing.T, pkgslist ...PackagePath) (*Node, *mock.Serv // Call NewDevNode with no package should work cfg := DefaultNodeConfig(gnoenv.RootDir()) cfg.PackagesPathList = pkgslist - node, err := NewDevNode(ctx, logger, emitter, cfg) + cfg.Emitter = emitter + cfg.Logger = logger + node, err := NewDevNode(ctx, cfg) require.NoError(t, err) assert.Len(t, node.ListPkgs(), len(pkgslist)) diff --git a/contribs/gnodev/pkg/rawterm/keypress.go b/contribs/gnodev/pkg/rawterm/keypress.go index 48f8367a65b..45c64c999dd 100644 --- a/contribs/gnodev/pkg/rawterm/keypress.go +++ b/contribs/gnodev/pkg/rawterm/keypress.go @@ -16,10 +16,15 @@ const ( KeyCtrlL KeyPress = '\x0c' // Ctrl+L KeyCtrlO KeyPress = '\x0f' // Ctrl+O KeyCtrlR KeyPress = '\x12' // Ctrl+R + KeyCtrlS KeyPress = '\x13' // Ctrl+S KeyCtrlT KeyPress = '\x14' // Ctrl+T KeyA KeyPress = 'A' + KeyE KeyPress = 'E' KeyH KeyPress = 'H' + KeyI KeyPress = 'I' + KeyN KeyPress = 'N' + KeyP KeyPress = 'P' KeyR KeyPress = 'R' ) @@ -43,6 +48,8 @@ func (k KeyPress) String() string { return "Ctrl+O" case KeyCtrlR: return "Ctrl+R" + case KeyCtrlS: + return "Ctrl+S" case KeyCtrlT: return "Ctrl+T" default: diff --git a/contribs/gnofaucet/Makefile b/contribs/gnofaucet/Makefile index 03ef409cac9..b520a17182f 100644 --- a/contribs/gnofaucet/Makefile +++ b/contribs/gnofaucet/Makefile @@ -5,3 +5,9 @@ install: .PHONY: build build: go build -o build/gnofaucet . + +test: + @echo "XXX: add tests" + +lint: + @echo "XXX: add lint" diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index dba13876eb7..0662de5f82f 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,10 +1,12 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.21 +go 1.22 + +toolchain go1.22.4 require ( github.com/gnolang/faucet v0.2.1 - github.com/gnolang/gno v0.0.0-20240429120125-3832b1312d7d + github.com/gnolang/gno v0.1.0-nightly.20240627 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/time v0.5.0 @@ -21,32 +23,33 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.0 // indirect github.com/rs/xid v1.5.0 // indirect - go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 // indirect - go.opentelemetry.io/otel/metric v1.26.0 // indirect - go.opentelemetry.io/otel/sdk v1.26.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.26.0 // indirect - go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.22.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 8e6cff33158..29dc3efcb76 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -47,8 +47,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gnolang/faucet v0.2.1 h1:ohYzZEuweqwTJBiutZbWFgb4w2slI6/JKobGPmiwG0g= github.com/gnolang/faucet v0.2.1/go.mod h1:cOWkLBbgJ9mjOiYW4DBrZ0P5m0+14pTLaERF7v/5b+4= -github.com/gnolang/gno v0.0.0-20240429120125-3832b1312d7d h1:vD4URFbnv8UL/NG8fX5pjVPRyQ1tvCtqTzU5I996EtA= -github.com/gnolang/gno v0.0.0-20240429120125-3832b1312d7d/go.mod h1:vbm5sS6KZj2rkwJ9C6AtdpPZB7aZDhfZ3lJUC+qa/to= +github.com/gnolang/gno v0.1.0-nightly.20240627 h1:ZfVzzr2nadX5NLsZ76WN2CLb7TTuMJMwCdqTBZXVLGo= +github.com/gnolang/gno v0.1.0-nightly.20240627/go.mod h1:WCOCLF55BgFd2cw/Rrqa4zk9nK4AVVvbrPKprhkG4Wo= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= @@ -77,13 +77,11 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -92,8 +90,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/linxGnu/grocksdb v1.6.20 h1:C0SNv12/OBr/zOdGw6reXS+mKpIdQGb/AkZWjHYnO64= -github.com/linxGnu/grocksdb v1.6.20/go.mod h1:IbTMGpmWg/1pg2hcG9LlxkqyqiJymdCweaUrzsLRFmg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -123,18 +119,20 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= -go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= -go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= -go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -148,8 +146,8 @@ go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= @@ -159,8 +157,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r 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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -173,34 +171,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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= -google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnofaucet/serve.go b/contribs/gnofaucet/serve.go index c82155d0ce7..837e620c5aa 100644 --- a/contribs/gnofaucet/serve.go +++ b/contribs/gnofaucet/serve.go @@ -154,7 +154,7 @@ func execServe(ctx context.Context, cfg *serveCfg, io commands.IO) error { // Parse static gas values. // It is worth noting that this is temporary, // and will be removed once gas estimation is enabled - // on Gno.land + // on gno.land gasFee := std.MustParseCoin(defaultGasFee) gasWanted, err := strconv.ParseInt(defaultGasWanted, 10, 64) diff --git a/contribs/gnokeykc/Makefile b/contribs/gnokeykc/Makefile new file mode 100644 index 00000000000..f953631b529 --- /dev/null +++ b/contribs/gnokeykc/Makefile @@ -0,0 +1,8 @@ +install: + go install . + +test: + @echo "XXX: add tests" + +lint: + @echo "XXX: add lint" diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index d368402a3c3..d86c71558fb 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,6 +1,8 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.21 +go 1.22 + +toolchain go1.22.4 replace github.com/gnolang/gno => ../.. @@ -14,18 +16,20 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // 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/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 github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -36,23 +40,25 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel v1.25.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.25.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 4027483eb1c..e20600ea557 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -30,6 +30,8 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= @@ -53,8 +55,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -77,11 +79,13 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/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= @@ -91,6 +95,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -134,36 +140,40 @@ 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.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -174,35 +184,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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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 v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnokeykc/main.go b/contribs/gnokeykc/main.go index 2065725e5b6..5b5e99a876a 100644 --- a/contribs/gnokeykc/main.go +++ b/contribs/gnokeykc/main.go @@ -4,6 +4,7 @@ import ( "context" "os" + "github.com/gnolang/gno/gno.land/pkg/keyscli" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" @@ -15,7 +16,7 @@ func main() { wrappedio := &wrappedIO{IO: stdio} baseCfg := client.DefaultBaseOptions baseCfg.Home = gnoenv.HomeDir() - cmd := client.NewRootCmdWithBaseConfig(wrappedio, baseCfg) + cmd := keyscli.NewRootCmd(wrappedio, baseCfg) cmd.AddSubCommands(newKcCmd(stdio)) cmd.Execute(context.Background(), os.Args[1:]) diff --git a/contribs/gnomd/Makefile b/contribs/gnomd/Makefile new file mode 100644 index 00000000000..f953631b529 --- /dev/null +++ b/contribs/gnomd/Makefile @@ -0,0 +1,8 @@ +install: + go install . + +test: + @echo "XXX: add tests" + +lint: + @echo "XXX: add lint" diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 1c22a07da31..8bc352d4848 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -1,6 +1,8 @@ module github.com/gnolang/gno/contribs/gnomd -go 1.21 +go 1.22 + +toolchain go1.22.4 require github.com/MichaelMure/go-term-markdown v0.1.4 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000000..c642cdbbe8e --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,10 @@ +all: build lint + +# Build the linter +build: + 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 . 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 new file mode 100644 index 00000000000..40c79bd91f6 Binary files /dev/null and b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif 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 index 4da21bc863b..4882aabdfde 100644 Binary files a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif and b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif differ diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno index 2a9856bb6d6..bbdf84f8a9f 100644 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno @@ -1,3 +1,5 @@ +package mytoken + import ( "std" "strings" @@ -7,7 +9,8 @@ import ( ) var ( - mytoken *grc20.AdminToken + banker *grc20.Banker + mytoken grc20.Token admin std.Address ) @@ -17,9 +20,11 @@ func init() { admin = std.PrevRealm().Addr() // Set token name, symbol and number of decimals - mytoken = grc20.NewAdminToken("My Token", "TKN", 4) + banker = grc20.NewBanker("My Token", "TKN", 4) // Mint 1 million tokens to admin - mytoken.Mint(admin, 1000000*10000) -} + 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 index 3ce2346b903..71616feba15 100644 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno @@ -1,3 +1,12 @@ +package mytoken + +import ( + "std" + "strings" + + "gno.land/p/demo/ufmt" +) + // TotalSupply returns the total supply of mytoken func TotalSupply() uint64 { return mytoken.TotalSupply() @@ -10,84 +19,39 @@ func Decimals() uint { // BalanceOf returns the balance mytoken for `account` func BalanceOf(account std.Address) uint64 { - balance, err := mytoken.BalanceOf(account) - if err != nil { - panic(err) - } - - return balance + return mytoken.BalanceOf(account) } // Allowance returns the allowance of spender on owner's balance func Allowance(owner, spender std.Address) uint64 { - allowance, err := mytoken.Allowance(owner, spender) - if err != nil { - panic(err) - } - - return allowance + return mytoken.Allowance(owner, spender) } // Transfer transfers amount from caller to recipient func Transfer(recipient std.Address, amount uint64) { - caller := std.PrevRealm().Addr() - if err := mytoken.Transfer(caller, recipient, amount); err != nil { - panic(err) - } + checkErr(mytoken.Transfer(recipient, amount)) } // Approve approves amount of caller's tokens to be spent by spender func Approve(spender std.Address, amount uint64) { - caller := std.PrevRealm().Addr() - if err := mytoken.Approve(caller, spender, amount); err != nil { - panic(err) - } + checkErr(mytoken.Approve(spender, amount)) } // TransferFrom transfers `amount` of tokens from `from` to `to` func TransferFrom(from, to std.Address, amount uint64) { - caller := std.PrevRealm().Addr() - - if amount <= 0 { - panic("transfer amount must be greater than zero") - } - - if err := mytoken.TransferFrom(caller, from, to, amount); err != nil { - panic(err) - } + 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()) - - if amount <= 0 { - panic("mint amount must be greater than zero") - } - - if err := mytoken.Mint(address, amount); err != nil { - panic(err) - } + 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()) - - if amount <= 0 { - panic("burn amount must be greater than zero") - } - - if err := mytoken.Burn(address, amount); err != nil { - panic(err) - } -} - -// assertIsAdmin asserts the address is the admin of token -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") - } + checkErr(banker.Burn(address, amount)) } // Render renders the state of the realm @@ -108,3 +72,17 @@ func Render(path string) string { 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/creating-grc721/mynonfungibletoken-1.gno b/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno deleted file mode 100644 index ff567f47a9c..00000000000 --- a/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno +++ /dev/null @@ -1,104 +0,0 @@ -package mynft - -import ( - "github.com/gnolang/gno/examples/gno.land/p/demo/grc/grc721" - "std" - //"gno.land/p/demo/grc/grc721" -) - -var ( - mynft *grc721.IGRC721 - 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 - mynft = grc721.NewBasicNFT("My NFT", "MNFT") -} - -func BalanceOf(owner std.Address) uint64 { - balance, err := mynft.BalanceOf(owner) - if err != nil { - panic(err) - } - - return balance -} - -func OwnerOf(tid grc721.TokenID) std.Address { - owner, err := mynft.OwnerOf(tid) - if err != nil { - panic(err) - } - - return owner -} - -func IsApprovedForAll(owner, operator std.Address) bool { - return mynft.IsApprovedForAll(owner, operator) -} - -func GetApproved(tid grc721.TokenID) std.Address { - addr, err := mynft.GetApproved(tid) - if err != nil { - panic(err) - } - - return addr -} - -func Approve(to std.Address, tid grc721.TokenID) { - err := mynft.Approve(to, tid) - if err != nil { - panic(err) - } -} - -func SetApprovalForAll(operator std.Address, approved bool) { - err := mynft.SetApprovalForAll(operator, approved) - if err != nil { - panic(err) - } -} - -func TransferFrom(from, to std.Address, tid grc721.TokenID) { - err := mynft.TransferFrom(from, to, tid) - if err != nil { - panic(err) - } -} - -func Mint(to std.Address, tid grc721.TokenID) { - assertIsAdmin(std.PrevRealm().Addr()) - err := mynft.Mint(to, tid) - if err != nil { - panic(err) - } -} - -func Burn(tid grc721.TokenID) { - assertIsAdmin(std.PrevRealm().Addr()) - err := mynft.Burn(tid) - if err != nil { - panic(err) - } -} - -func Render(path string) string { - switch { - case path == "": - return mynft.RenderHome() - default: - return "404\n" - } -} - -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") - } -} diff --git a/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno b/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno deleted file mode 100644 index 8092596ae0a..00000000000 --- a/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno +++ /dev/null @@ -1,102 +0,0 @@ -func mintNNFT(owner std.Address, n uint64) { - count := my.TokenCount() - for i := count; i < count+n; i++ { - tid := grc721.TokenID(ufmt.Sprintf("%d", i)) - mynonfungibletoken.Mint(owner, tid) - } -} - -// Getters - -func BalanceOf(user users.AddressOrName) uint64 { - balance, err := mynonfungibletoken.BalanceOf(user.Resolve()) - if err != nil { - panic(err) - } - - return balance -} - -func OwnerOf(tid grc721.TokenID) std.Address { - owner, err := mynonfungibletoken.OwnerOf(tid) - if err != nil { - panic(err) - } - - return owner -} - -func IsApprovedForAll(owner, user users.AddressOrName) bool { - return mynonfungibletoken.IsApprovedForAll(owner.Resolve(), user.Resolve()) -} - -func GetApproved(tid grc721.TokenID) std.Address { - addr, err := mynonfungibletoken.GetApproved(tid) - if err != nil { - panic(err) - } - - return addr -} - -// Setters - -func Approve(user users.AddressOrName, tid grc721.TokenID) { - err := mynonfungibletoken.Approve(user.Resolve(), tid) - if err != nil { - panic(err) - } -} - -func SetApprovalForAll(user users.AddressOrName, approved bool) { - err := mynonfungibletoken.SetApprovalForAll(user.Resolve(), approved) - if err != nil { - panic(err) - } -} - -func TransferFrom(from, to users.AddressOrName, tid grc721.TokenID) { - err := mynonfungibletoken.TransferFrom(from.Resolve(), to.Resolve(), tid) - if err != nil { - panic(err) - } -} - -// Admin - -func Mint(to users.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := mynonfungibletoken.Mint(to.Resolve(), tid) - if err != nil { - panic(err) - } -} - -func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := mynonfungibletoken.Burn(tid) - if err != nil { - panic(err) - } -} - -// Render - -func Render(path string) string { - switch { - case path == "": - return mynonfungibletoken.RenderHome() - default: - return "404\n" - } -} - -// Util - -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") - } -} diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno index bf2a64743d0..98b0ae4ed7a 100644 --- a/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno +++ b/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno @@ -64,6 +64,8 @@ func (p Poll) VoteCount() (int, int) { if vote == true { yay = yay + 1 } + + return false }) return yay, p.Voters().Size() - yay } diff --git a/docs/concepts/concepts.md b/docs/concepts/concepts.md index 4c9949ddd0d..452627747b5 100644 --- a/docs/concepts/concepts.md +++ b/docs/concepts/concepts.md @@ -5,4 +5,4 @@ id: concepts # Concepts Welcome to the **Concepts** section for Gno. This section outlines the most important -concepts related to Gno & Gno.land. \ No newline at end of file +concepts related to Gno & gno.land. diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index 1152146ce51..45872dd1d63 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -597,7 +597,7 @@ In this example, `GetPost` is a function that retrieves a post from the loading any other posts. In the future, we plan to add built-in "map" support that will match the -efficienty of an `avl.Tree` while offering a more intuitive API. Until then, if +efficiency of an `avl.Tree` while offering a more intuitive API. Until then, if you're dealing with a compact dataset, it's probably best to use slices. For larger datasets where you need to quickly retrieve elements by keys, `avl.Tree` is the way to go. @@ -679,7 +679,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). +Read about how to use the Banker module [here](stdlibs/banker.md). #### GRC20 tokens @@ -701,12 +701,11 @@ best of both worlds, you can wrap a Coins into a GRC20 compatible token. ```go import "gno.land/p/demo/grc/grc20" -var fooToken grc20.AdminToken = grc20.NewAdminToken("Foo Token", "FOO", 4) +var fooToken = grc20.NewBanker("Foo Token", "FOO", 4) func MyBalance() uint64 { caller := std.PrevRealm().Addr() - balance, _ := fooToken.BalanceOf(caller) - return balance + return fooToken.BalanceOf(caller) } ``` @@ -723,7 +722,7 @@ See also: https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/wu - -## `p/` - "Pure" packages - -A `p/` package denotes a "pure" package within the system. These packages are -crafted as self-contained units of code, capable of being independently imported -and utilized. - -Unlike `r/` realms, `p/` packages do not possess states or assets. They are -designed specifically to be called by other packages, whether those packages are -pure or realms. - -## `r/` - "Realm" packages - -An `r/` realm designates a package endowed with advanced capabilities, referred -to as a "realm". - -Realms can accommodate a diverse array of data and functionalities, including -Bank State, Data State, and Address. - -They are purposed to furnish and expose features for both user-initiated calls -and as components invoked by other realm packages. diff --git a/docs/gno-infrastructure/gno-infrastructure.md b/docs/gno-infrastructure/gno-infrastructure.md new file mode 100644 index 00000000000..aa301fa79d4 --- /dev/null +++ b/docs/gno-infrastructure/gno-infrastructure.md @@ -0,0 +1,9 @@ +--- +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/getting-started/local-setup/premining-balances.md b/docs/gno-infrastructure/premining-balances.md similarity index 87% rename from docs/getting-started/local-setup/premining-balances.md rename to docs/gno-infrastructure/premining-balances.md index b7f2139f508..0755674543e 100644 --- a/docs/getting-started/local-setup/premining-balances.md +++ b/docs/gno-infrastructure/premining-balances.md @@ -6,16 +6,20 @@ id: premining-balances ## Overview -In this tutorial, you will gain an understanding on how to premine native currency on a local Gno.land chain. +In this tutorial, you will gain an understanding on how to premine native currency on a local gno.land chain. Additionally, you will understand how to query the account balance after you premine it. Premining balance is the process of making sure some accounts (addresses) have specific funds when the chain initially launches. In the context of local chain deployments, premine balances are used to ensure the user accounts (developers) have ample funds to interact with the chain and facilitate contract deployments. - + ## Prerequisites -- **`gnoland` and `gnokey` set up. Reference the [Installation](local-setup.md#3-installing-other-gno-tools) guide +- **`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 @@ -53,7 +57,6 @@ An example of how this looks like in the initial `genesis.json` file after the c ``` 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 @@ -67,7 +70,6 @@ desired `ugnot` balance. 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 @@ -77,8 +79,7 @@ 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) +![gnoland start](../assets/getting-started/local-setup/setting-up-funds/gnoland-start.gif) ## 3. Check the account balance @@ -94,13 +95,11 @@ Let's break down this command: 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) + ![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 new file mode 100644 index 00000000000..f15f6bb59e2 --- /dev/null +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -0,0 +1,110 @@ +--- +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 new file mode 100644 index 00000000000..c345b49724a --- /dev/null +++ b/docs/gno-infrastructure/validators/faq.md @@ -0,0 +1,182 @@ +--- +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 generate `genesis.json`? + +`genesis.json` is the file that is used to create the initial state of the chain. To generate `genesis.json`, use +the `gnoland genesis generate` command. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-generate-flags) for various flags that allow you to +manipulate the file. + +:::warning + +Editing generated genesis.json manually is extremely dangerous. It may corrupt chain initial state which leads chain to +not start + +::: + +### How do I add or remove validators from `genesis.json`? + +Validators inside `genesis.json` will be included in the validator set at genesis. To manipulate the genesis validator +set, use the `gnoland genesis validator` command with the `add` or `remove` subcommands. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-validator-flags) for flags that allow you to +configure the name or the voting power of the validator. + +### How do I add the balance information to the `genesis.json`? + +You may premine coins to various addresses. To modify the balances of addresses at genesis, use +the `gnoland genesis balances` command with the `add` or `remove` subcommands. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-balances-add-flags) for various flags that allow you +to update the entire balance sheet with a file or modify the balance of a single address. + +:::info + +Not only `ugnot`, but other coins are accepted. However, be aware that coins other than `ugnot` may not work(send, and +etc.) properly. + +::: + +### How do I initialize `gno secrets`? + +The `gno secrets init` command allows you to initialize the private information required to run the validator, including +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 new file mode 100644 index 00000000000..7128a8c6b75 --- /dev/null +++ b/docs/gno-infrastructure/validators/overview.md @@ -0,0 +1,98 @@ +--- +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/w2MpVEunxr) +- [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 new file mode 100644 index 00000000000..d13fa5d216e --- /dev/null +++ b/docs/gno-infrastructure/validators/running-a-validator.md @@ -0,0 +1,27 @@ +--- +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 new file mode 100644 index 00000000000..5d440a86684 --- /dev/null +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -0,0 +1,454 @@ +--- +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` binary, 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 +``` + +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 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 +gnoland genesis 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 +gnoland genesis 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 +gnoland genesis 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 +gnoland genesis 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 index 9ec63af4301..4d32f86e9ef 100644 --- a/docs/gno-tooling/cli/faucet/faucet.md +++ b/docs/gno-tooling/cli/faucet/faucet.md @@ -8,7 +8,7 @@ id: running-a-faucet 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](../../../getting-started/local-setup/premining-balances.md). +haven't [premined a balance beforehand](../../../gno-infrastructure/premining-balances.md). ## Prerequisites @@ -19,14 +19,15 @@ haven't [premined a balance beforehand](../../../getting-started/local-setup/pre 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](../../../getting-started/local-setup/premining-balances.md) to a high value. +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](../../../getting-started/local-setup/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](../../../getting-started/local-setup/setting-up-a-local-chain.md). +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 diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md index 97a4cc2e5ce..4a1880822fc 100644 --- a/docs/gno-tooling/cli/gnodev.md +++ b/docs/gno-tooling/cli/gnodev.md @@ -8,19 +8,21 @@ 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. +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). + [`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 @@ -72,6 +74,21 @@ 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 @@ -100,24 +117,32 @@ 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 | +| 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.md b/docs/gno-tooling/cli/gnokey.md index 8479e9c112d..38afa750799 100644 --- a/docs/gno-tooling/cli/gnokey.md +++ b/docs/gno-tooling/cli/gnokey.md @@ -124,8 +124,8 @@ gnokey query {QUERY_PATH} | `bank/balances/{ADDRESS}` | Returns balances of an account. | `gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` | | `vm/qfuncs` | Returns public facing function signatures as JSON. | `gnokey query vm/qfuncs --data "gno.land/r/demo/boards"` | | `vm/qfile` | Returns the file bytes, or list of files if directory. | `gnokey query vm/qfile --data "gno.land/r/demo/boards"` | -| `vm/qrender` | Calls .Render(path) in readonly mode. | `gnokey query vm/qrender --data "gno.land/r/demo/boards"` | -| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | `gnokey query vm/qeval --data "gno.land/r/demo/boards GetBoardIDFromName("my_board")"` | +| `vm/qrender` | Calls .Render(path) in readonly mode. | `gnokey query vm/qrender --data "gno.land/r/demo/boards:"` | +| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | `gnokey query vm/qeval --data "gno.land/r/demo/boards.GetBoardIDFromName("my_board")"` | | `vm/store` | (not yet supported) Fetches items from the store. | - | | `vm/package` | (not yet supported) Fetches a package's files. | - | @@ -134,8 +134,6 @@ gnokey query {QUERY_PATH} | Name | Type | Description | |----------|-----------|------------------------------------------| | `data` | UInt8 \[] | Queries data bytes. | -| `height` | Int64 | (not yet supported) Queries height. | -| `prove` | Boolean | (not yet supported) Proves query result. | ## Sign and Broadcast a Transaction @@ -179,6 +177,7 @@ gnokey maketx addpkg \ | `broadcast` | Boolean | Broadcasts the transaction. | | `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | | `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | +| `sponsor` | String | Bech32 address of the sponsor who pay gas fee for the transaction | #### **makeTx AddPackage Options** @@ -190,7 +189,7 @@ gnokey maketx addpkg \ ### `call` -This subcommand lets you call a public function. +This subcommand lets you call any exported function. ```bash # Register @@ -207,6 +206,20 @@ gnokey maketx call \ > unsigned.tx ``` +:::warning `call` is a state-changing message + +All exported functions, including `Render()`, can be called in two main ways: +`call` and [`query vm/qeval`](#query). + +With `call`, any state change that happened in the function being called will be +applied and persisted in on the blockchain, and the gas used for this call will +be subtracted from the caller balance. + +As opposed to this, an ABCI query, such as `vm/qeval` will not persist state +changes and does not cost gas, only evaluating the expression in read-only mode. + +::: + #### **SignBroadcast Options** | Name | Type | Description | @@ -217,6 +230,7 @@ gnokey maketx call \ | `broadcast` | Boolean | Broadcasts the transaction. | | `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | | `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | +| `sponsor` | String | Bech32 address of the sponsor who pay gas fee for the transaction | #### **makeTx Call Options** @@ -256,6 +270,7 @@ gnokey maketx send \ | `broadcast` | Boolean | Broadcasts the transaction. | | `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | | `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | +| `sponsor` | String | Bech32 address of the sponsor who pay gas fee for the transaction | #### **makeTx Send Options** diff --git a/docs/gno-tooling/cli/gnoland.md b/docs/gno-tooling/cli/gnoland.md index 9bd722b0f2c..18175871d90 100644 --- a/docs/gno-tooling/cli/gnoland.md +++ b/docs/gno-tooling/cli/gnoland.md @@ -4,28 +4,375 @@ id: gno-tooling-gnoland # gnoland -## Run a Gnoland Node +`gnoland` is the gno.land blockchain client binary, which is capable of managing +node working files, as well as starting the blockchain client itself. -Start a node on the Gnoland blockchain with the following command. +### 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 genesis \ [flags] [\...] + +Gno `genesis.json` manipulation suite for managing genesis parameters. + +#### SUBCOMMANDS + +| Name | Description | +|-------------|---------------------------------------------| +| `generate` | Generates a fresh `genesis.json`. | +| `validator` | Validator set management in `genesis.json`. | +| `verify` | Verifies a `genesis.json`. | +| `balances` | Manages `genesis.json` account balances. | +| `txs` | Manages the initial genesis transactions. | + +### gnoland genesis generate [flags] + +Generates a node's `genesis.json` based on specified parameters. + +#### FLAGS + +| Name | Type | Description | +|------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `block-max-data-bytes` | Int | The max size of the block data.(default: `2000000`) | +| `block-max-gas` | Int | The max gas limit for the block. (default: `100000000`) | +| `block-max-tx-bytes` | Int | The max size of the block transaction. (default: `1000000`) | +| `block-time-itoa` | Int | The block time itoa (in ms). (default: `100`) | +| `chain-id` | String | The ID of the chain. (default: `dev`) | +| `genesis-time` | Int | The genesis creation time. (default: `utc now timestamp`) | +| `output-path` : | String | The output path for the `genesis.json`. If the genesis-time of the Genesis File is set to a future time, the chain will automatically start at that time if the node is online. (default: `./genesis.json`) | + +### gnoland genesis validator \ [flags] + +Manipulates the `genesis.json` validator set. + +#### SUBCOMANDS + +| Name | Description | +|----------|----------------------------------------------| +| `add` | Adds a new validator to the `genesis.json`. | +| `remove` | Removes a validator from the `genesis.json`. | + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|------------------------------------------------------------| +| `address` | String | The gno bech32 address of the validator. | +| `genesis-path` | String | The path to the `genesis.json`. (default `./genesis.json`) | + +### gnoland genesis validator add [flags] + +Adds a new validator to the `genesis.json`. + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|-----------------------------------------------------------------| +| `address` | String | The gno bech32 address of the validator. | +| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json`) | +| `name` | String | The name of the validator (must be unique). | +| `power` | Uint | The voting power of the validator (must be > 0). (default: `1`) | +| `pub-key` | String | The bech32 string representation of the validator's public key. | + +```bash +gnoland genesis validator add \ +-address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h \ +-name test1 \ +-pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplmcmggxyxyrch0zcyg684yxmerullv3l6hmau58sk4eyxskmny9h7lsnz + +Validator with address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h added to genesis file +``` + +### gnoland genesis validator remove [flags] + +Removes a validator from the `genesis.json`. + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|-------------------------------------------------------------| +| `address` | String | The gno bech32 address of the validator. | +| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json)` | + +```bash +gnoland genesis validator remove \ +-address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h + +Validator with address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h removed from genesis file +``` + +### gnoland genesis verify \ [flags] [\…] + +Verifies a `genesis.json`. + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|-----------------------------------------------------------| +| `genesis-path` | String | The path to the `genesis.json`. (default: `genesis.json`) | + +### gnoland genesis balances \ [flags] [\…] + +Manages `genesis.json` account balances. + +#### SUBCOMMANDS + +| Name | Description | +|----------|--------------------------------------------------------| +| `add` | Adds the balance information. | +| `remove` | Removes the balance information of a specific account. | + +### gnoland genesis balances add [flags] + +#### FLAGS + +| Name | Type | Description | +|-----------------|--------|--------------------------------------------------------------------------------------------| +| `balance-sheet` | String | The path to the balance file containing addresses in the format `
=ugnot`. | +| `genesis-path` | String | The path to the `genesis.json` (default: `./genesis.json`) | +| `parse-export` | String | The path to the transaction export containing a list of transactions (JSONL). | +| `single` | String | The direct balance addition in the format `
=ugnot`. | + +```bash +gnoland genesis balances add \ +-single g1rzuwh5frve732k4futyw45y78rzuty4626zy6h=100ugnot + +1 pre-mines saved + +g1rzuwh5frve732k4futyw45y78rzuty4626zy6h:{[24 184 235 209 35 102 125 21 90 169 226 200 234 208 158 56 197 197 146 186] [{%!d(string=ugnot) 100}]}ugnot +``` + +### gnoland balances remove [flags] + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|---------------------------------------------------------------------------------------------| +| `address` | String | The address of the account whose balance information should be removed from `genesis.json`. | +| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json`) | ```bash -gnoland +gnoland genesis balances remove \ +-address=g1rzuwh5frve732k4futyw45y78rzuty4626zy6h + +Pre-mine information for address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h removed ``` -### **Sub Commands** -| Command | Description | -| --------- | ----------------- | -| `start` | Run the full node | +### gnoland txs \ [flags] [\…] + +Manages genesis transactions through input files. + +#### SUBCOMMANDS + +| Name | Description | +|----------|---------------------------------------------------| +| `add` | Imports transactions into the `genesis.json`. | +| `remove` | Removes the transactions from the `genesis.json`. | +| `export` | Exports the transactions from the `genesis.json`. | + +### gnoland secrets \ [flags] [\…] + +The gno secrets manipulation suite for managing the validator key, p2p key and +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] [\] -### **Options** +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. -| Name | Type | Description | -|----------------------------| ------- | --------------------------------------------------------------------------------------- | -| `chainid` | String | The id of the chain (default: `dev`). | -| `genesis-balances-file` | String | The initial GNOT distribution file (default: `./gnoland/genesis/genesis_balances.txt`). | -| `genesis-remote` | String | Replacement '%%REMOTE%%' in genesis (default: `"localhost:26657"`). | -| `genesis-txs-file` | String | Initial txs to be executed (default: `"./gnoland/genesis/genesis_txs.jsonl"`). | -| `data-dir` | String | directory for config and data (default: `gnoland-data`). | -| `skip-failing-genesis-txs` | Boolean | Skips transactions that fail from the `genesis-txs-file` | -| `skip-start` | Boolean | Quits after initialization without starting the node. | +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 index a03bf84a5bf..290d56820d0 100644 --- a/docs/gno-tooling/gno-tooling.md +++ b/docs/gno-tooling/gno-tooling.md @@ -4,5 +4,28 @@ 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. \ No newline at end of file +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/connecting-from-go.md b/docs/how-to-guides/connecting-from-go.md index cb6ed0547b6..4926f700a4d 100644 --- a/docs/how-to-guides/connecting-from-go.md +++ b/docs/how-to-guides/connecting-from-go.md @@ -2,9 +2,9 @@ id: connect-from-go --- -# How to connect a Go app to Gno.land +# How to connect a Go app to gno.land -This guide will show you how to connect to a Gno.land network from your Go application, +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: @@ -14,7 +14,7 @@ For this guide, we will build a small Go app that will: - Read on-chain state ## Prerequisites -- A local Gno.land keypair generated using +- A local gno.land keypair generated using [gnokey](../getting-started/local-setup/working-with-key-pairs.md) ## Setup @@ -48,9 +48,9 @@ go get github.com/gnolang/gno/gno.land/pkg/gnoclient 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. +to a gno.land chain. -```go +```go type Client struct { Signer Signer // Signer for transaction authentication RPCClient rpcclient.Client // gnolang/gno/tm2/pkg/bft/rpc/client @@ -59,7 +59,7 @@ type Client struct { ### Signer -The `Signer` provides functionality to sign transactions with a Gno.land keypair. +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. @@ -69,7 +69,7 @@ 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. +The `RPCCLient` provides connectivity to a gno.land network via HTTP or WebSockets. ## Initialize the Signer @@ -93,7 +93,7 @@ func main() { Keybase: keybase, Account: "", // Name of your keypair in keybase Password: "", // Password to decrypt your keypair - ChainID: "", // id of Gno.land chain + ChainID: "", // id of gno.land chain } } ``` @@ -101,19 +101,23 @@ func main() { 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`](../reference/gnoclient/signer.md#func-signerfrombip39) function. +- `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 +You can initialize the RPC Client used to connect to the gno.land network with the following line: ```go -rpc := rpcclient.NewHTTPClient("") +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/rpc-endpoints.md#network-configurations) page. +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: @@ -135,11 +139,14 @@ func main() { Keybase: keybase, Account: "", // Name of your keypair in keybase Password: "", // Password to decrypt your keypair - ChainID: "", // id of Gno.land chain + ChainID: "", // id of gno.land chain } // Initialize the RPC client - rpc := rpcclient.NewHTTPClient("") + rpc, err := rpcclient.NewHTTPClient("") + if err != nil { + panic(err) + } // Initialize the gnoclient client := gnoclient.Client{ @@ -149,7 +156,7 @@ func main() { } ``` -We can now communicate with the Gno.land chain. Let's explore some of the functionality +We can now communicate with the gno.land chain. Let's explore some of the functionality `gnoclient` provides. ## Query account info from a chain @@ -158,6 +165,13 @@ 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 @@ -188,7 +202,7 @@ We are now ready to send a transaction to the chain. ## Sending a transaction -A Gno.land transaction consists of two main parts: +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. @@ -261,7 +275,7 @@ To see all functionality the `gnoclient` package provides, see the gnoclient Congratulations 🎉 -You've just built a small demo app in Go that connects to a Gno.land chain +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). diff --git a/docs/how-to-guides/creating-grc20.md b/docs/how-to-guides/creating-grc20.md index 8b950074f81..13f22fcc6a2 100644 --- a/docs/how-to-guides/creating-grc20.md +++ b/docs/how-to-guides/creating-grc20.md @@ -17,7 +17,7 @@ Our **GRC20** Realm will have the following functionality: ## 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 the +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) @@ -33,7 +33,8 @@ import ( ) var ( - mytoken *grc20.AdminToken + banker *grc20.Banker + mytoken grc20.Token admin std.Address ) @@ -43,20 +44,23 @@ func init() { admin = std.PrevRealm().Addr() // Set token name, symbol and number of decimals - mytoken = grc20.NewAdminToken("My Token", "TKN", 4) + banker = grc20.NewBanker("My Token", "TKN", 4) // Mint 1 million tokens to admin - mytoken.Mint(admin, 1000000*10000) + 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, `mytoken`, and assigns it to a -pointer to the GRC20 token type, `grc20.AdminToken`, +- 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 new GRC20 token, and set its name, symbol, and -decimal values, +- 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 @@ -65,15 +69,16 @@ 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 { @@ -82,120 +87,89 @@ func Decimals() uint { ``` 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 { - balance, err := mytoken.BalanceOf(account) - if err != nil { - panic(err) - } - - return balance + 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 { - allowance, err := mytoken.Allowance(owner, spender) - if err != nil { - panic(err) - } - - return allowance + return mytoken.Allowance(owner, spender) } ``` -Calling the `Allowance` method will return the amount `spender` is allowed to spend -from `owner`'s balance. +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) { - caller := std.PrevRealm().Addr() - if err := mytoken.Transfer(caller, recipient, amount); err != nil { - panic(err) - } + checkErr(mytoken.Transfer(recipient, amount)) } ``` -Calling the `Transfer` method transfers amount of token from the calling account -to the recipient account. +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) { - caller := std.PrevRealm().Addr() - if err := mytoken.Approve(caller, spender, amount); err != nil { - panic(err) - } + 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(sender, recipient std.Address, amount uint64) { - caller := std.PrevRealm().Addr() - - if amount <= 0 { - panic("transfer amount must be greater than zero") - } - - if err := mytoken.TransferFrom(caller, from, to, amount); err != nil { - panic(err) - } +// 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()) - - if amount <= 0 { - panic("mint amount must be greater than zero") - } - - if err := mytoken.Mint(address, amount); err != nil { - panic(err) - } + 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()) - - if amount <= 0 { - panic("burn amount must be greater than zero") - } - - if err := mytoken.Burn(address, amount); err != nil { - panic(err) - } + 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 asserts the address is the admin of token -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") - } + 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 { @@ -219,7 +193,8 @@ func Render(path string) string { 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/km7Ja6WDQoL). +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 🎉 diff --git a/docs/how-to-guides/creating-grc721.md b/docs/how-to-guides/creating-grc721.md deleted file mode 100644 index 4bf93c9c3eb..00000000000 --- a/docs/how-to-guides/creating-grc721.md +++ /dev/null @@ -1,200 +0,0 @@ ---- -id: creating-grc721 ---- - -# How to create a GRC721 Token (NFT) - -## Overview - -This guide shows you how to write a simple **GRC721** Smart Contract, or rather -a [Realm](../concepts/realms.md), in [Gno](../concepts/gno-language.md). -For actually deploying the Realm, please see the [deployment](deploy.md) guide. - -Our **GRC721** Realm will have the following functionality: - -- Minting a configurable amount of tokens. -- Keeping track of total token supply. -- Fetching the balance of an account. - -## 1. Importing token package - -For this realm, we'll want to import the `grc721` package as this will include -the main functionality of our NFT realm. The package can be found the -`gno.land/p/demo/grc/grc721` path. - -[embedmd]:# (../assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno go) -```go -package mynft - -import ( - "std" - - "gno.land/p/demo/grc/grc721" -) - -var ( - mytoken *grc20.AdminToken - 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 - mynft = grc721.NewBasicNFT("My NFT", "MNFT") - - // Mint 1 million tokens to admin - mytoken.Mint(admin, 1000000*10000) -} -``` - -In this code preview, we have: - -- Defined and set the value of `mynonfungibletoken` (type `*grc721.basicNFT`) to equal the result of creating a new - token and configuring its name and symbol. -- Defined and set the value of local variable `admin` to point to a specific gno.land address of type `std.Address`. -- Minted 5 `mynonfungibletoken (MNFT)` and set the administrator as the owner of these tokens - -## 2. Adding token functionality - -The following section will be about introducing Public functions to expose functionality imported from -the [grc721 package](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/grc/grc721). - -[embedmd]:# (../assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno go) -```go -func mintNNFT(owner std.Address, n uint64) { - count := my.TokenCount() - for i := count; i < count+n; i++ { - tid := grc721.TokenID(ufmt.Sprintf("%d", i)) - mynonfungibletoken.Mint(owner, tid) - } -} - -// Getters - -func BalanceOf(user users.AddressOrName) uint64 { - balance, err := mynonfungibletoken.BalanceOf(user.Resolve()) - if err != nil { - panic(err) - } - - return balance -} - -func OwnerOf(tid grc721.TokenID) std.Address { - owner, err := mynonfungibletoken.OwnerOf(tid) - if err != nil { - panic(err) - } - - return owner -} - -func IsApprovedForAll(owner, user users.AddressOrName) bool { - return mynonfungibletoken.IsApprovedForAll(owner.Resolve(), user.Resolve()) -} - -func GetApproved(tid grc721.TokenID) std.Address { - addr, err := mynonfungibletoken.GetApproved(tid) - if err != nil { - panic(err) - } - - return addr -} - -// Setters - -func Approve(user users.AddressOrName, tid grc721.TokenID) { - err := mynonfungibletoken.Approve(user.Resolve(), tid) - if err != nil { - panic(err) - } -} - -func SetApprovalForAll(user users.AddressOrName, approved bool) { - err := mynonfungibletoken.SetApprovalForAll(user.Resolve(), approved) - if err != nil { - panic(err) - } -} - -func TransferFrom(from, to users.AddressOrName, tid grc721.TokenID) { - err := mynonfungibletoken.TransferFrom(from.Resolve(), to.Resolve(), tid) - if err != nil { - panic(err) - } -} - -// Admin - -func Mint(to users.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := mynonfungibletoken.Mint(to.Resolve(), tid) - if err != nil { - panic(err) - } -} - -func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := mynonfungibletoken.Burn(tid) - if err != nil { - panic(err) - } -} - -// Render - -func Render(path string) string { - switch { - case path == "": - return mynonfungibletoken.RenderHome() - default: - return "404\n" - } -} - -// Util - -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") - } -} -``` - -Detailing what is happening in the above code: - -- Calling the **local** `mintNNFT` method would mint a configurable number of tokens to the provided owner's account. -- Calling the `BalanceOf` method would return the total balance of an account. -- Calling the `OwnerOf` method would return the owner of the token based on the ID that is passed into the method. -- Calling the `IsApprovedByAll` method will return true if an operator is approved for all operations by the owner; - otherwise, returns false. -- Calling the `GetApproved` method will return the address approved to operate on the token. -- Calling the `Approve` method would approve the input address for a particular token. -- Calling the `SetApprovalForAll` method would approve an operating account to operate on all tokens. -- Calling the `TransferFrom` method would transfer a configurable amount of token from an account that granted approval - to another account, either owned or unowned. -- Calling the `Mint` method would create a configurable number of tokens by the administrator. -- Calling the `Burn` method would destroy a configurable number of tokens by the administrator. -- Calling the `Render` method on success would invoke - a [`RenderHome`](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic_nft.gno#L353) - method on the `grc721` instance we instantiated at the top of the file; this method returns a formatted string that - includes the token: symbol, supply and account balances (`balances avl.Tree`) which is a mapping denoted - as: `OwnerAddress -> TokenCount`; otherwise returns false and renders a `404`; you can find more information about - this `Render` method and how it's used [here](../concepts/realms.md). -- Finally, we provide a local function to assert that the calling account is in fact the owner, otherwise panic. This is - a very important function that serves to prevent abuse by non-administrators. - -## Conclusion - -That's it 🎉 - -You have successfully built a simple GRC721 Realm that is ready to be deployed on the Gno chain and called by users. -In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside -tools like a wallet application. diff --git a/docs/how-to-guides/deploy.md b/docs/how-to-guides/deploy.md index 54a0ef582ce..620a5664f7c 100644 --- a/docs/how-to-guides/deploy.md +++ b/docs/how-to-guides/deploy.md @@ -21,7 +21,7 @@ For this, check out the [**Deployment from a local environment**](#deployment-fr ### Prerequisites -- **A Gno.land-compatible wallet, such as [Adena](https://adena.app)** +- **A gno.land-compatible wallet, such as [Adena](https://adena.app)** ### Using Gno Playground @@ -137,4 +137,4 @@ subcommand, view the `gnokey` [reference](../gno-tooling/cli/gnokey.md#addpkg). That's it 🎉 -You have now successfully deployed a realm/package to a Gno.land chain. +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 index eeaee9c24f1..5c3425f2c54 100644 --- a/docs/how-to-guides/how-to-guides.md +++ b/docs/how-to-guides/how-to-guides.md @@ -6,4 +6,4 @@ id: 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. \ No newline at end of file +code to the chain, creating a GRC20 or GRC721 token, etc. diff --git a/docs/how-to-guides/interact-with-gnoland.md b/docs/how-to-guides/interact-with-gnoland.md deleted file mode 100644 index 527e20981a7..00000000000 --- a/docs/how-to-guides/interact-with-gnoland.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -id: interact-with-gnoland ---- - -# Interact with Gno.land - -This tutorial will teach you how to interact with the gno.land blockchain by creating an account and calling various realms to send transactions on the network. - -## Prerequisites - -- [Installation](../getting-started/local-setup/local-setup.md) - -## Create an Account - -In order to interact with Gnoland, you need an account that you will use to sign and send transactions. You may create a new account with `gnokey generate` or recover an existing one with `gnokey add`. Confirm that your account was successfully added with `gnokey list` to display all accounts registered in the key base of your device. - -```bash -gnokey generate # create a new seed phrase (mnemonic) - -gnokey add -recover {your_account_name} # registers a key with the name set as the value you put in {your_account_name} with a seed phrase - -gnokey list # check the list of keys -``` - -## Register As a User - -```bash -gnokey maketx call \ - -gas-fee="1ugnot" \ - -gas-wanted="5000000" \ - -broadcast="true" \ - -remote="staging.gno.land:36657" \ - -chainid="test3" \ - -pkgpath="gno.land/r/demo/users" \ - -func="Register" \ - -args="" \ - -args="my_account" \ # (must be at least 6 characters, lowercase alphanumeric with underscore) - -args="" \ - -send="200000000ugnot" \ - my-account - -# username: must be at least 6 characters, lowercase alphanumeric with underscore -``` - -> **Note:** With a user registration fee of 200 GNOT and a gas fee that ranges up to 2 GNOT, you must have around 202 GNOT to complete this transaction. After registering as a user, you may replace your address with your `username` when developing or publishing a realm package. - -## Get Account Information - -```bash -# Get account information -gnokey query -remote="staging.gno.land:36657" "auth/accounts/{address}" - -# Get account balance -gnokey query -remote="staging.gno.land:36657" "bank/balances/{address}" - -# Get /r/demo/boards user information -gnokey query -remote="staging.gno.land:36657" -data "gno.land/r/demo/users -my_account" "vm/qrender" -``` - -## Send Tokens - -The following command will send 1,000,000 ugnot (= 1 GNOT) to the address specified in the `to` argument. - -```bash -# Creates and broadcast a token transfer transaction -gnokey maketx send \ - -gas-fee="1ugnot" \ - -gas-wanted="5000000" \ - -broadcast="true" \ - -remote="staging.gno.land:36657" \ - -chainid="test3" \ - -to="{address}" \ # g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 - -send="{amount}{denom}" \ # 1234ugnot - my-account -``` - -## Create a Board - -Try creating a board called `my_board` on the `gno.land/r/demo/boards` realm with the following command: - -```bash -# Calls the CreateBoard function of gno.land/r/demo/boards -gnokey maketx call \ - -gas-fee="1ugnot" \ - -gas-wanted="5000000" \ - -broadcast="true" \ - -remote "staging.gno.land:36657" \ - -chainid="test3" \ - -pkgpath="gno.land/r/demo/boards" \ - -func="CreateBoard" \ - -args="my_board" \ - my-account -``` diff --git a/docs/how-to-guides/simple-library.md b/docs/how-to-guides/simple-library.md index 1ae231251d0..62ff2bd2c0f 100644 --- a/docs/how-to-guides/simple-library.md +++ b/docs/how-to-guides/simple-library.md @@ -34,7 +34,7 @@ and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=har ::: We discussed Gno folder structures more in detail in -the [simple Smart Contract guide](simple-contract.md#1-setting-up-the-work-directory). +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: @@ -127,7 +127,7 @@ var listOfTapas = []string{ // GetTapaSuggestion randomly selects and returns a tapa suggestion func GetTapaSuggestion(userInput string) string { - // Create a pseudorandom number depending on the block height. + // 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) diff --git a/docs/how-to-guides/testing-gno.md b/docs/how-to-guides/testing-gno.md index 3ba734abba4..e32a9435711 100644 --- a/docs/how-to-guides/testing-gno.md +++ b/docs/how-to-guides/testing-gno.md @@ -184,4 +184,4 @@ in the [Standard Library](../concepts/stdlibs/stdlibs.md) reference section. 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. \ No newline at end of file +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 index a46d6688fa9..f844f8ab7c8 100644 --- a/docs/how-to-guides/write-simple-dapp.md +++ b/docs/how-to-guides/write-simple-dapp.md @@ -2,7 +2,7 @@ id: write-simple-dapp --- -# How to write a simple dApp on Gno.land +# How to write a simple dApp on gno.land ## Overview @@ -94,6 +94,8 @@ func (p Poll) VoteCount() (int, int) { if vote == true { yay = yay + 1 } + + return false }) return yay, p.Voters().Size() - yay } @@ -133,7 +135,6 @@ The realm will contain the following functionality: package poll import ( - "bytes" "std" "gno.land/p/demo/avl" diff --git a/docs/overview.md b/docs/overview.md index 2e96da17438..3619e507dba 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -7,10 +7,10 @@ version of the Go programming language called Gno." # Overview -## What is Gno.land? +## 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 (Gno for short). +version of the Go programming language called Gno. ### Key Features and Technology @@ -21,36 +21,37 @@ version of the Go programming language called Gno (Gno for short). 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 +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: +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. + 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. + 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: +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. + 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 +## 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. +- 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. +- Tutorials aimed at beginners to build fundamental skills for developing in gno.land. diff --git a/docs/reference/gno-js-client/gno-provider.md b/docs/reference/gno-js-client/gno-provider.md index a5248349d35..df808106cc3 100644 --- a/docs/reference/gno-js-client/gno-provider.md +++ b/docs/reference/gno-js-client/gno-provider.md @@ -39,7 +39,7 @@ Fetches public facing function signatures * `height` **number** the height for querying. If omitted, the latest height is used (optional, default `0`) -Returns **Promise** +Returns **Promise** #### Usage diff --git a/docs/reference/gno-js-client/gno-wallet.md b/docs/reference/gno-js-client/gno-wallet.md index 7f7c44cd9b0..247c3d52878 100644 --- a/docs/reference/gno-js-client/gno-wallet.md +++ b/docs/reference/gno-js-client/gno-wallet.md @@ -63,7 +63,7 @@ Deploys the specified package / realm #### Parameters * `gnoPackage` **MemPackage** the package / realm to be deployed -* `funds` **Map** the denomination -> value map for funds +* `funds` **Map** the denomination -> value map for funds * `fee` **TxFee** the custom transaction fee, if any Returns **Promise** diff --git a/docs/reference/gnoclient/client.md b/docs/reference/gnoclient/client.md deleted file mode 100644 index 0fbef3f5f93..00000000000 --- a/docs/reference/gnoclient/client.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -id: client ---- - -# Client - -## type [Client]() - -`Client` provides an interface for interacting with the blockchain. It is the main -struct of the `gnoclient` package, exposing all APIs used to communicate with a -Gno.land chain. - -```go -type Client struct { - Signer Signer // Signer for transaction authentication - RPCClient rpcclient.Client // RPC client for blockchain communication -} -``` - -### func \(\*Client\) [AddPackage]() - -```go -func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`AddPackage` executes one or more [AddPackage](#type-msgaddpackage) calls on the blockchain. - -### func \(\*Client\) [Block]() - -```go -func (c *Client) Block(height int64) (*ctypes.ResultBlock, error) -``` - -`Block` gets the latest block at height, if any. Height must be larger than 0. - -### func \(\*Client\) [BlockResult]() - -```go -func (c *Client) BlockResult(height int64) (*ctypes.ResultBlockResults, error) -``` - -`BlockResult` gets the block results at height, if any. Height must be larger than 0. - -### func \(\*Client\) [LatestBlockHeight]() - -```go -func (c *Client) LatestBlockHeight() (int64, error) -``` - -`LatestBlockHeight` gets the latest block height on the chain. - -### func \(\*Client\) [Call]() - -```go -func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`Call` executes a one or more [MsgCall](#type-msgcall) calls on the blockchain. - -### func \(\*Client\) [Send]() - -```go -func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`Send` executes one or more [MsgSend](#type-msgsend) calls on the blockchain. - -### func \(\*Client\) [Run]() - -```go -func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`Run` executes a one or more MsgRun calls on the blockchain. - -### func \(\*Client\) [QEval]() - -```go -func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.ResultABCIQuery, error) -``` - -`QEval` evaluates the given expression with the realm code at `pkgPath`. -The `pkgPath` should include the prefix like `gno.land/`. The expression is -usually a function call like `Render("")`. - -### func \(*Client\) [Query]() - -```go -func (c *Client) Query(cfg QueryCfg) (*ctypes.ResultABCIQuery, error) -``` - -`Query` performs a generic query on the blockchain. - -### func \(*Client\) [QueryAccount]() - -```go -func (c *Client) QueryAccount(addr crypto.Address) (*std.BaseAccount, *ctypes.ResultABCIQuery, error) -``` - -`QueryAccount` retrieves account information for a given address. - -### func \(*Client\) [QueryAppVersion]() - -```go -func (c *Client) QueryAppVersion() (string, *ctypes.ResultABCIQuery, error) -``` - -`QueryAppVersion` retrieves information about the Gno.land app version. - -### func \(*Client\) [Render]() - -```go -func (c *Client) Render(pkgPath string, args string) (string, *ctypes.ResultABCIQuery, error) -``` - -`Render` calls the Render function for pkgPath with optional args. The `pkgPath` -should include the prefix like `gno.land/`. This is similar to using a browser -URL `/:` where `` doesn't have the prefix like -`gno.land/`. - -## type [BaseTxCfg]() - -`BaseTxCfg` defines the base transaction configuration, shared by all message -types. - -```go -type BaseTxCfg struct { - GasFee string // Gas fee - GasWanted int64 // Gas wanted - AccountNumber uint64 // Account number - SequenceNumber uint64 // Sequence number - Memo string // Memo -} -``` - -## type [MsgAddPackage]() - -`MsgAddPackage` \- syntax sugar for `vm.MsgAddPackage`. - -```go -type MsgAddPackage struct { - Package *std.MemPackage // Package to add - Deposit string // Coin deposit -} -``` - -## type [MsgCall]() - -`MsgCall` \- syntax sugar for `vm.MsgCall`. - -```go -type MsgCall struct { - PkgPath string // Package path - FuncName string // Function name - Args []string // Function arguments - Send string // Send amount -} -``` - -## type [MsgRun]() - -`MsgRun` \- syntax sugar for `vm.MsgRun`. - -```go -type MsgRun struct { - Package *std.MemPackage // Package to run - Send string // Send amount -} -``` - -## type [MsgSend]() - -`MsgSend` \- syntax sugar for `bank.MsgSend`. - -```go -type MsgSend struct { - ToAddress crypto.Address // Send to address - Send string // Send amount -} -``` - -## type [QueryCfg]() - -`QueryCfg` contains configuration options for performing ABCI queries. - -```go -type QueryCfg struct { - Path string // Query path - Data []byte // Query data - rpcclient.ABCIQueryOptions // ABCI query options -} -``` \ No newline at end of file diff --git a/docs/reference/gnoclient/gnoclient.md b/docs/reference/gnoclient/gnoclient.md index f5baa9fc03a..0c6c0d87308 100644 --- a/docs/reference/gnoclient/gnoclient.md +++ b/docs/reference/gnoclient/gnoclient.md @@ -22,4 +22,13 @@ your Go code To add Gnoclient to your Go project, run the following command: ```bash go get github.com/gnolang/gno/gno.land/pkg/gnoclient -``` \ No newline at end of file +``` + +## 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/gnoclient/signer.md b/docs/reference/gnoclient/signer.md deleted file mode 100644 index 75c69a9f7c2..00000000000 --- a/docs/reference/gnoclient/signer.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: signer ---- - -# Signer - -`Signer` is an interface that provides functionality for signing transactions. -The signer can be created from a local keybase, or from a bip39 mnemonic phrase. - -Useful types and functions when using the `Signer` can be found below. - -## type [Signer]() - -`Signer` provides an interface for signing transactions. - -```go -type Signer interface { - Sign(SignCfg) (*std.Tx, error) // Signs a transaction and returns a signed tx ready for broadcasting. - Info() keys.Info // Returns key information, including the address. - Validate() error // Checks whether the signer is properly configured. -} -``` - -## type [SignCfg]() - -`SignCfg` provides the signing configuration, containing the unsigned transaction -data, account number, and account sequence. - -```go -type SignCfg struct { - UnsignedTX std.Tx - SequenceNumber uint64 - AccountNumber uint64 -} -``` - -## type [SignerFromKeybase]() - -`SignerFromKeybase` represents a signer created from a Keybase. - -```go -type SignerFromKeybase struct { - Keybase keys.Keybase // Stores keys in memory or on disk - Account string // Account name or bech32 format - Password string // Password for encryption - ChainID string // Chain ID for transaction signing -} -``` - -### func \(SignerFromKeybase\) [Info]() - -```go -func (s SignerFromKeybase) Info() keys.Info -``` - -`Info` gets keypair information. - -### func \(SignerFromKeybase\) [Sign]() - -```go -func (s SignerFromKeybase) Sign(cfg SignCfg) (*std.Tx, error) -``` - -`Sign` implements the Signer interface for SignerFromKeybase. - -### func \(SignerFromKeybase\) [Validate]() - -```go -func (s SignerFromKeybase) Validate() error -``` - -`Validate` checks if the signer is properly configured. - -## func [SignerFromBip39]() - -```go -func SignerFromBip39(mnemonic string, chainID string, passphrase string, account uint32, index uint32) (Signer, error) -``` - -`SignerFromBip39` creates a `Signer` from an in-memory keybase with a single default -account, derived from the given mnemonic. -This can be useful in scenarios where storing private keys in the filesystem -isn't feasible, or for generating a signer for testing. - -> Using `keys.NewKeyBaseFromDir()` to get a keypair from local storage is -recommended where possible, as it is more secure. \ No newline at end of file diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 89ad4f7b990..a2f83f2bbc6 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -34,7 +34,7 @@ id: go-gno-compatibility Generics are currently not implemented. -Note that Gno does not support shadowing of built-in types. +Note that Gno does not support shadowing of built-in types. While the following built-in typecasting assignment would work in Go, this is not supported in Gno. ```go @@ -205,7 +205,7 @@ Legend: | math/big | `tbd` | | math/bits | `full` | | math/cmplx | `tbd` | -| math/rand | `todo` | +| math/rand | `full`[^9] | | mime | `tbd` | | mime/multipart | `tbd` | | mime/quotedprintable | `tbd` | @@ -291,6 +291,7 @@ Legend: determinism. Concurrent functionality (such as `time.Ticker`) is not implemented. [^8]: `crypto/ed25519` is currently only implemented for `Verify`, which should still cover a majority of use cases. A full implementation is welcome. +[^9]: `math/rand` in Gno ports over Go's `math/rand/v2`. ## Tooling (`gno` binary) diff --git a/docs/reference/network-config.md b/docs/reference/network-config.md index 0d933db39f4..0dacc8e80d7 100644 --- a/docs/reference/network-config.md +++ b/docs/reference/network-config.md @@ -4,16 +4,16 @@ id: network-config # Network configurations -| Network | RPC Endpoint | Chain ID | -|-------------|------------------------------------|---------------| -| Portal Loop | https://rpc.gno.land:443 | `portal-loop` | -| Testnet 4 | upcoming | upcoming | -| Testnet 3 | https://rpc.test3.gno.land:443 | `test3` | -| Staging | https://rpc.staging.gno.land:36657 | `test3` | +| Network | RPC Endpoint | Chain ID | +|-------------|-----------------------------------|---------------| +| Portal Loop | https://rpc.gno.land:443 | `portal-loop` | +| Test4 | https://rpc.test4.gno.land:443 | `test4` | +| Test3 | https://rpc.test3.gno.land:443 | `test3` | +| Staging | http://rpc.staging.gno.land:36657 | `staging` | ### WebSocket endpoints All networks follow the same pattern for websocket connections: ```shell wss:///websocket -``` \ No newline at end of file +``` diff --git a/docs/reference/reference.md b/docs/reference/reference.md index acef30f1083..a635370c62f 100644 --- a/docs/reference/reference.md +++ b/docs/reference/reference.md @@ -5,4 +5,4 @@ id: reference # Reference Welcome to the **Reference** section for Gno. This section outlines common APIs, -network configurations, client usages, etc. \ No newline at end of file +network configurations, client usages, etc. diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 06c40d63afc..f8d3cba13bb 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -41,18 +41,6 @@ std.Emit("MyEvent", "myKey1", "myValue1", "myKey2", "myValue2") ``` --- -## CurrentRealmPath -```go -func CurrentRealmPath() string -``` -Returns the path of the realm it is called in. - -#### Usage -```go -realmPath := std.CurrentRealmPath() // gno.land/r/demo/users -``` ---- - ## GetChainID ```go func GetChainID() string @@ -117,9 +105,8 @@ origPkgAddr := std.GetOrigPkgAddr() ```go func CurrentRealm() Realm ``` -Returns current Realm object. +Returns current [Realm](realm.md) object. -[//]: # (todo link to realm type explanation) #### Usage ```go currentRealm := std.CurrentRealm() @@ -130,7 +117,7 @@ currentRealm := std.CurrentRealm() ```go func PrevRealm() Realm ``` -Returns the previous caller realm (can be code or user realm). If caller is a +Returns the previous caller [realm](realm.md) (can be code or user realm). If caller is a user realm, `pkgpath` will be empty. #### Usage diff --git a/docs/reference/stdlibs/std/coin.md b/docs/reference/stdlibs/std/coin.md index f2e1cf694b4..3675e1365f6 100644 --- a/docs/reference/stdlibs/std/coin.md +++ b/docs/reference/stdlibs/std/coin.md @@ -10,31 +10,167 @@ 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.Coin{"ugnot", 100} +coin := std.NewCoin("ugnot", 100) coin.String() // 100ugnot ``` --- + ## IsGTE -Checks if the amount of `other` Coin is greater or equal than amount of Coin `c` it was called upon. -If coins compared are not of the same denomination, `IsGTE` will panic. +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.Coin{"ugnot", 150} -coin2 := std.Coin{"ugnot", 100} +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) coin1.IsGTE(coin2) // true coin2.IsGTE(coin1) // false -``` \ No newline at end of file +``` +--- + +## 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 index 0f813178330..cc366937153 100644 --- a/docs/reference/stdlibs/std/coins.md +++ b/docs/reference/stdlibs/std/coins.md @@ -8,11 +8,28 @@ id: coins ```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. @@ -24,7 +41,7 @@ coins.String() // 100ugnot,150foo,200bar --- ### AmountOf -Returns **int64** amount of specified coin within the `Coins` set it was called upon. Returns `0` if coin specified coin does not exist in the set. +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 diff --git a/docs/reference/stdlibs/std/realm.md b/docs/reference/stdlibs/std/realm.md new file mode 100644 index 00000000000..0c99b7134ea --- /dev/null +++ b/docs/reference/stdlibs/std/realm.md @@ -0,0 +1,41 @@ +--- +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 {...} +``` + +## 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() {...} +``` diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md index 8c9146c81a1..e3e87ea7262 100644 --- a/docs/reference/stdlibs/std/testing.md +++ b/docs/reference/stdlibs/std/testing.md @@ -5,32 +5,27 @@ id: testing # Testing ```go -func TestCurrentRealm() string 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 ``` -## TestCurrentRealm -```go -func TestCurrentRealm() string -``` -Returns the current realm path. - -#### Usage -```go -currentRealmPath := std.TestCurrentRealm() -``` --- ## 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) @@ -38,6 +33,7 @@ std.TestSkipHeights(100) --- ## TestSetOrigCaller + ```go func TestSetOrigCaller(addr Address) ``` @@ -45,23 +41,26 @@ Sets the current caller of the transaction to **addr**. #### Usage ```go -std.TestSetOrigCaller("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") +std.TestSetOrigCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) ``` --- ## TestSetOrigPkgAddr + ```go func TestSetOrigPkgAddr(addr Address) ``` -Sets the current realm/package address to **addr**. +Sets the call entry realm address to **addr**. #### Usage ```go -std.TestSetOrigPkgAddr("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +std.TestSetOrigPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) ``` + --- ## TestSetOrigSend + ```go func TestSetOrigSend(sent, spent Coins) ``` @@ -74,17 +73,78 @@ 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 := "g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec" +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("")) +// 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/tm2-js-client/Provider/ws-provider.md b/docs/reference/tm2-js-client/Provider/ws-provider.md index ef91f45d4e2..9d4f2390c28 100644 --- a/docs/reference/tm2-js-client/Provider/ws-provider.md +++ b/docs/reference/tm2-js-client/Provider/ws-provider.md @@ -18,7 +18,7 @@ Creates a new instance of the WebSocket Provider #### Usage ```ts -new WSProvider('ws://staging.gno.land:36657/ws'); +new WSProvider('ws://staging.gno.land:26657/ws'); // provider with WS connection is created ``` @@ -30,7 +30,7 @@ with the WS provider #### Usage ```ts -const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); +const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); wsProvider.closeConnection(); // WS connection is now closed @@ -52,7 +52,7 @@ Returns **Promise>** ```ts const request: RPCRequest = // ... -const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); +const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); wsProvider.sendRequest(request); // request is sent over the open WS connection @@ -73,7 +73,7 @@ Returns **Result** ```ts const response: RPCResponse = // ... -const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); +const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); wsProvider.parseResponse(response); // response is parsed @@ -88,7 +88,7 @@ Returns **Promise** #### Usage ```ts -const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); +const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); await wsProvider.waitForOpenConnection() // status of the connection is: CONNECTED diff --git a/examples/Makefile b/examples/Makefile index 39a51a32112..578b4faf15b 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -27,6 +27,7 @@ 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 @@ -55,15 +56,10 @@ clean: find . \( -name "*.gno.gen.go" -or -name ".*.gno.gen_test.go" \) -delete .PHONY: fmt -GOFMT_FLAGS ?= -w +GNOFMT_FLAGS ?= -w fmt: - go run -modfile ../misc/devdeps/go.mod mvdan.cc/gofumpt $(GOFMT_FLAGS) `find . -name "*.gno"` - -.PHONY: imports -GOIMPORTS_FLAGS ?= -w -imports: - $(rundep) golang.org/x/tools/cmd/goimports $(GOIMPORTS_FLAGS) . + go run ../gnovm/cmd/gno fmt $(GNOFMT_FLAGS) ./... .PHONY: tidy tidy: - find . -name "gno.mod" -execdir go run github.com/gnolang/gno/gnovm/cmd/gno mod tidy \; + go run github.com/gnolang/gno/gnovm/cmd/gno mod tidy -v --recursive diff --git a/examples/gno.land/p/demo/acl/acl_test.gno b/examples/gno.land/p/demo/acl/acl_test.gno index 3d5b8a41891..53804eab4e1 100644 --- a/examples/gno.land/p/demo/acl/acl_test.gno +++ b/examples/gno.land/p/demo/acl/acl_test.gno @@ -5,6 +5,8 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" ) func Test(t *testing.T) { @@ -110,31 +112,23 @@ func Test(t *testing.T) { func shouldHasRole(t *testing.T, dir *Directory, addr std.Address, role string) { t.Helper() check := dir.HasRole(addr, role) - if !check { - t.Errorf("%q should has role %q", addr.String(), role) - } + uassert.Equal(t, true, check, ufmt.Sprintf("%s should has role %s", addr.String(), role)) } func shouldNotHasRole(t *testing.T, dir *Directory, addr std.Address, role string) { t.Helper() check := dir.HasRole(addr, role) - if check { - t.Errorf("%q should not has role %q", addr.String(), role) - } + uassert.Equal(t, false, check, ufmt.Sprintf("%s should not has role %s", addr.String(), role)) } func shouldHasPerm(t *testing.T, dir *Directory, addr std.Address, verb string, resource string) { t.Helper() check := dir.HasPerm(addr, verb, resource) - if !check { - t.Errorf("%q should has perm for %q - %q", addr.String(), verb, resource) - } + uassert.Equal(t, true, check, ufmt.Sprintf("%s should has perm for %s - %s", addr.String(), verb, resource)) } func shouldNotHasPerm(t *testing.T, dir *Directory, addr std.Address, verb string, resource string) { t.Helper() check := dir.HasPerm(addr, verb, resource) - if check { - t.Errorf("%q should not has perm for %q - %q", addr.String(), verb, resource) - } + uassert.Equal(t, false, check, ufmt.Sprintf("%s should not has perm for %s - %s", addr.String(), verb, resource)) } diff --git a/examples/gno.land/p/demo/acl/gno.mod b/examples/gno.land/p/demo/acl/gno.mod index 176cde637bd..15d9f078048 100644 --- a/examples/gno.land/p/demo/acl/gno.mod +++ b/examples/gno.land/p/demo/acl/gno.mod @@ -3,4 +3,6 @@ module gno.land/p/demo/acl require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/avl/tree_test.gno b/examples/gno.land/p/demo/avl/tree_test.gno index 76b108933c6..8f6efcc5bad 100644 --- a/examples/gno.land/p/demo/avl/tree_test.gno +++ b/examples/gno.land/p/demo/avl/tree_test.gno @@ -1,8 +1,6 @@ package avl -import ( - "testing" -) +import "testing" func TestNewTree(t *testing.T) { tree := NewTree() diff --git a/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index e91788ac8eb..aff79ffabc6 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -25,67 +25,25 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "key0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "value0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -140,13 +98,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -183,19 +160,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "6da365f0d6cacbcdf53cd5a4b125803cddce08c2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "091729e38bda8724bce4c314f9624b91af679459", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // }, // { @@ -208,27 +179,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "f216afe7b5a17f4ebdbb98dceccedbc22e237596", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ff1a50d8489090af37a2c7766d659f0d717939b5", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -236,7 +220,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -244,15 +228,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -266,7 +269,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -274,9 +277,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "10", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -310,9 +313,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "15", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -322,31 +325,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6c9948281d4c60b2d95233b76388d54d8b1a2fad", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } -// } // } // ] // } diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index cdd56a5ad89..3b6d40d5ecd 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { // "T": { @@ -79,13 +79,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "143aebc820da33550f7338723fb1e2eec575b196", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={ // "Fields": [ // { // "T": { @@ -122,19 +141,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "7a8a63e17a567d7b0891ac89d5cd90072a73787d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // }, // { @@ -147,30 +160,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2e733a8e9e74fe14f0a5d10fb0f6728fa53d052d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ab5a297f4eb033d88bdf1677f4dc151ccb9fde9f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ // "Fields": [ // { // "T": { @@ -207,19 +233,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // }, // { @@ -232,27 +252,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fe8afd501233fb95375016199f0443b3c6ab1fbc", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -260,7 +293,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "6", +// "ModTime": "9", // "RefCount": "2" // }, // "Parent": null, @@ -268,15 +301,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -290,7 +342,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -298,9 +350,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "10", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -334,9 +386,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "15", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -346,32 +398,8 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c5eefc40ed065461b4a920c1349ed734ffdead8f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } -// } // } // ] // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/examples/gno.land/p/demo/avl/z_2_filetest.gno b/examples/gno.land/p/demo/avl/z_2_filetest.gno index 65181bffcac..43067c31e8f 100644 --- a/examples/gno.land/p/demo/avl/z_2_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_2_filetest.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:16]={ // "Fields": [ // { // "T": { @@ -78,13 +78,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:16", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "db333c89cd6773709e031f1f4e4ed4d3fed66c11", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:16" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={ // "Fields": [ // { // "T": { @@ -121,19 +140,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "849a50d6c78d65742752e3c89ad8dd556e2e63cb", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "213baed7e3326f2403b5f30e5d4397510ba4f37d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // }, // { @@ -146,30 +159,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b4fc2fdd2d0fe936c87ed2ace97136cffeed207f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "be751422ef4c2bc068a456f9467d2daca27db8ca", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a1160b0060ad752dbfe5fe436f7734bb19136150", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={ // "Fields": [ // { // "T": { @@ -206,19 +232,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "fd95e08763159ac529e26986d652e752e78b6325", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "af4d0b158681d85eb2a7f6888b39a05ca7b790ee", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // }, // { @@ -231,30 +251,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "3ecdcf148fe2f9e97b72a3bedf303b2ba56d4f4b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ef853d70e334fd2c807d6c2c751da1fcd1e5ad58", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "63126557dba88f8556f7a0ccbbfc1d218ae7a302", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "Fields": [ // { // "T": { @@ -266,27 +299,22 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "d31c7e797793e03ffe0bbcb72f963264f8300d22", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "3a5af0895c2c45b8a5e894644bcd689f1fdc4785", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "ModTime": "10", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" // } // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:6] diff --git a/examples/gno.land/p/demo/bf/bf_test.gno b/examples/gno.land/p/demo/bf/bf_test.gno index 1be4a86ac33..c55ad3a316c 100644 --- a/examples/gno.land/p/demo/bf/bf_test.gno +++ b/examples/gno.land/p/demo/bf/bf_test.gno @@ -1,9 +1,6 @@ package bf -import ( - "bytes" - "testing" -) +import "testing" func TestExecuteBrainfuck(t *testing.T) { testCases := []struct { diff --git a/examples/gno.land/p/demo/blog/blog.gno b/examples/gno.land/p/demo/blog/blog.gno index 2c9c0e89359..aad2173b4b8 100644 --- a/examples/gno.land/p/demo/blog/blog.gno +++ b/examples/gno.land/p/demo/blog/blog.gno @@ -67,6 +67,8 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) { } p := post.(*Post) + res.Write("
" + "\n\n") + res.Write("# " + p.Title + "\n\n") res.Write(p.Body + "\n\n") res.Write("---\n\n") @@ -86,6 +88,7 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) { }) res.Write("\n") + res.Write("
") } func (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) { @@ -155,10 +158,10 @@ func (b *Blog) NewPost(publisher std.Address, slug, title, body, pubDate string, CreatedAt: parsedTime, } - return b.prepareAndSetPost(post) + return b.prepareAndSetPost(post, false) } -func (b *Blog) prepareAndSetPost(post *Post) error { +func (b *Blog) prepareAndSetPost(post *Post, edit bool) error { post.Title = strings.TrimSpace(post.Title) post.Body = strings.TrimSpace(post.Body) @@ -175,16 +178,18 @@ func (b *Blog) prepareAndSetPost(post *Post) error { post.Blog = b post.UpdatedAt = time.Now() - trimmedTitleKey := strings.Replace(post.Title, " ", "", -1) - pubDateKey := post.CreatedAt.Format(time.RFC3339) + trimmedTitleKey := getTitleKey(post.Title) + pubDateKey := getPublishedKey(post.CreatedAt) - // Cannot have two posts with same title key - if _, found := b.PostsAlphabetical.Get(trimmedTitleKey); found { - return ErrPostTitleExists - } - // Cannot have two posts with *exact* same timestamp - if _, found := b.PostsPublished.Get(pubDateKey); found { - return ErrPostPubDateExists + if !edit { + // Cannot have two posts with same title key + if _, found := b.PostsAlphabetical.Get(trimmedTitleKey); found { + return ErrPostTitleExists + } + // Cannot have two posts with *exact* same timestamp + if _, found := b.PostsPublished.Get(pubDateKey); found { + return ErrPostPubDateExists + } } // Store post under keys @@ -195,6 +200,22 @@ func (b *Blog) prepareAndSetPost(post *Post) error { return nil } +func (b *Blog) RemovePost(slug string) { + p, exists := b.Posts.Get(slug) + if !exists { + panic("post with specified slug doesn't exist") + } + + post := p.(*Post) + + titleKey := getTitleKey(post.Title) + publishedKey := getPublishedKey(post.CreatedAt) + + _, _ = b.Posts.Remove(slug) + _, _ = b.PostsAlphabetical.Remove(titleKey) + _, _ = b.PostsPublished.Remove(publishedKey) +} + func (b *Blog) GetPost(slug string) *Post { post, found := b.Posts.Get(slug) if !found { @@ -229,7 +250,7 @@ func (p *Post) Update(title, body, publicationDate string, authors, tags []strin } p.CreatedAt = parsedTime - return p.Blog.prepareAndSetPost(p) + return p.Blog.prepareAndSetPost(p, true) } func (p *Post) AddComment(author std.Address, comment string) error { diff --git a/examples/gno.land/p/demo/blog/util.gno b/examples/gno.land/p/demo/blog/util.gno index b4a8652af72..99b59772082 100644 --- a/examples/gno.land/p/demo/blog/util.gno +++ b/examples/gno.land/p/demo/blog/util.gno @@ -1,7 +1,18 @@ package blog -import "strings" +import ( + "strings" + "time" +) func breadcrumb(parts []string) string { return "# " + strings.Join(parts, " / ") + "\n\n" } + +func getTitleKey(title string) string { + return strings.Replace(title, " ", "", -1) +} + +func getPublishedKey(t time.Time) string { + return t.Format(time.RFC3339) +} diff --git a/examples/gno.land/p/demo/cford32/README.md b/examples/gno.land/p/demo/cford32/README.md index 30cc9372e55..f74a7f5861d 100644 --- a/examples/gno.land/p/demo/cford32/README.md +++ b/examples/gno.land/p/demo/cford32/README.md @@ -42,7 +42,7 @@ lexical ordering. However, values [0,2^34) have a "double encoding", which if mixed together lose the lexical ordering property. The Uint64 encoding is most useful for generating string versions of Uint64 IDs. -Practically, it allows you to retain sleek and compact IDs for your applcation +Practically, it allows you to retain sleek and compact IDs for your application for the first 2^34 (>17 billion) entities, while seamlessly rolling over to the full encoding should you exceed that. You are encouraged to use it unless you have a requirement or preferences for IDs consistently being always the same diff --git a/examples/gno.land/p/demo/cford32/cford32.gno b/examples/gno.land/p/demo/cford32/cford32.gno index effa32bef88..52d66db44df 100644 --- a/examples/gno.land/p/demo/cford32/cford32.gno +++ b/examples/gno.land/p/demo/cford32/cford32.gno @@ -41,7 +41,7 @@ // // The Uint64 encoding is most useful for generating string versions of Uint64 // IDs. Practically, it allows you to retain sleek and compact IDs for your -// applcation for the first 2^34 (>17 billion) entities, while seamlessly +// application for the first 2^34 (>17 billion) entities, while seamlessly // rolling over to the full encoding should you exceed that. You are encouraged // to use it unless you have a requirement or preferences for IDs consistently // being always the same size. diff --git a/examples/gno.land/p/demo/cford32/cford32_test.gno b/examples/gno.land/p/demo/cford32/cford32_test.gno index 1a17d64c856..6f269c1b9ed 100644 --- a/examples/gno.land/p/demo/cford32/cford32_test.gno +++ b/examples/gno.land/p/demo/cford32/cford32_test.gno @@ -6,7 +6,6 @@ import ( "fmt" "io" "math" - "strconv" "strings" "testing" ) diff --git a/examples/gno.land/p/demo/context/context.gno b/examples/gno.land/p/demo/context/context.gno new file mode 100644 index 00000000000..92d191012eb --- /dev/null +++ b/examples/gno.land/p/demo/context/context.gno @@ -0,0 +1,72 @@ +// Package context provides a minimal implementation of Go context with support +// for Value and WithValue. +// +// Adapted from https://github.com/golang/go/tree/master/src/context/. +// Copyright 2016 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 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{} +} + +// Empty returns a non-nil, empty context, similar with context.Background and +// context.TODO in Go. +func Empty() Context { + return &emptyCtx{} +} + +type emptyCtx struct{} + +func (ctx emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (ctx emptyCtx) String() string { + return "context.Empty" +} + +type valueCtx struct { + parent Context + key, val interface{} +} + +func (ctx *valueCtx) Value(key interface{}) interface{} { + if ctx.key == key { + return ctx.val + } + return ctx.parent.Value(key) +} + +func stringify(v interface{}) string { + switch s := v.(type) { + case stringer: + return s.String() + case string: + return s + } + return "non-stringer" +} + +type stringer interface { + String() string +} + +func (c *valueCtx) String() string { + return stringify(c.parent) + ".WithValue(" + + stringify(c.key) + ", " + + stringify(c.val) + ")" +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +func WithValue(parent Context, key, val interface{}) Context { + if key == nil { + panic("nil key") + } + // XXX: if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") } + return &valueCtx{parent, key, val} +} diff --git a/examples/gno.land/p/demo/context/context_test.gno b/examples/gno.land/p/demo/context/context_test.gno new file mode 100644 index 00000000000..0059f0d2a25 --- /dev/null +++ b/examples/gno.land/p/demo/context/context_test.gno @@ -0,0 +1,96 @@ +package context + +import "testing" + +func TestContextExample(t *testing.T) { + type favContextKey string + + k := favContextKey("language") + ctx := WithValue(Empty(), k, "Gno") + + if v := ctx.Value(k); v != nil { + if string(v) != "Gno" { + t.Errorf("language value should be Gno, but is %s", v) + } + } else { + t.Errorf("language key value was not found") + } + + if v := ctx.Value(favContextKey("color")); v != nil { + t.Errorf("color key was found") + } +} + +// otherContext is a Context that's not one of the types defined in context.go. +// This lets us test code paths that differ based on the underlying type of the +// Context. +type otherContext struct { + Context +} + +type ( + key1 int + key2 int +) + +// func (k key2) String() string { return fmt.Sprintf("%[1]T(%[1]d)", k) } + +var ( + k1 = key1(1) + k2 = key2(1) // same int as k1, different type + k3 = key2(3) // same type as k2, different int +) + +func TestValues(t *testing.T) { + check := func(c Context, nm, v1, v2, v3 string) { + if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { + t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) + } + if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { + t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) + } + if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { + t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) + } + } + + c0 := Empty() + check(c0, "c0", "", "", "") + + t.Skip() // XXX: depends on https://github.com/gnolang/gno/issues/2386 + + c1 := WithValue(Empty(), k1, "c1k1") + check(c1, "c1", "c1k1", "", "") + + /*if got, want := c1.String(), `context.Empty.WithValue(context_test.key1, c1k1)`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + }*/ + + c2 := WithValue(c1, k2, "c2k2") + check(c2, "c2", "c1k1", "c2k2", "") + + /*if got, want := fmt.Sprint(c2), `context.Empty.WithValue(context_test.key1, c1k1).WithValue(context_test.key2(1), c2k2)`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + }*/ + + c3 := WithValue(c2, k3, "c3k3") + check(c3, "c2", "c1k1", "c2k2", "c3k3") + + c4 := WithValue(c3, k1, nil) + check(c4, "c4", "", "c2k2", "c3k3") + + o0 := otherContext{Empty()} + check(o0, "o0", "", "", "") + + o1 := otherContext{WithValue(Empty(), k1, "c1k1")} + check(o1, "o1", "c1k1", "", "") + + o2 := WithValue(o1, k2, "o2k2") + check(o2, "o2", "c1k1", "o2k2", "") + + o3 := otherContext{c4} + check(o3, "o3", "", "c2k2", "c3k3") + + o4 := WithValue(o3, k3, nil) + check(o4, "o4", "", "c2k2", "") +} diff --git a/examples/gno.land/p/demo/context/gno.mod b/examples/gno.land/p/demo/context/gno.mod new file mode 100644 index 00000000000..a04ae1f91f8 --- /dev/null +++ b/examples/gno.land/p/demo/context/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/context diff --git a/examples/gno.land/p/demo/diff/diff.gno b/examples/gno.land/p/demo/diff/diff.gno new file mode 100644 index 00000000000..0f3da9b3f8e --- /dev/null +++ b/examples/gno.land/p/demo/diff/diff.gno @@ -0,0 +1,217 @@ +// The diff package implements the Myers diff algorithm to compute the edit distance +// and generate a minimal edit script between two strings. +// +// Edit distance, also known as Levenshtein distance, is a measure of the similarity +// between two strings. It is defined as the minimum number of single-character edits (insertions, +// deletions, or substitutions) required to change one string into the other. +package diff + +import ( + "strings" +) + +// EditType represents the type of edit operation in a diff. +type EditType uint8 + +const ( + // EditKeep indicates that a character is unchanged in both strings. + EditKeep EditType = iota + + // EditInsert indicates that a character was inserted in the new string. + EditInsert + + // EditDelete indicates that a character was deleted from the old string. + EditDelete +) + +// Edit represent a single edit operation in a diff. +type Edit struct { + // Type is the kind of edit operation. + Type EditType + + // Char is the character involved in the edit operation. + Char rune +} + +// MyersDiff computes the difference between two strings using Myers' diff algorithm. +// It returns a slice of Edit operations that transform the old string into the new string. +// This implementation finds the shortest edit script (SES) that represents the minimal +// set of operations to transform one string into the other. +// +// The function handles both ASCII and non-ASCII characters correctly. +// +// Time complexity: O((N+M)D), where N and M are the lengths of the input strings, +// and D is the size of the minimum edit script. +// +// Space complexity: O((N+M)D) +// +// In the worst case, where the strings are completely different, D can be as large as N+M, +// leading to a time and space complexity of O((N+M)^2). However, for strings with many +// common substrings, the performance is much better, often closer to O(N+M). +// +// Parameters: +// - old: the original string. +// - new: the modified string. +// +// Returns: +// - A slice of Edit operations representing the minimum difference between the two strings. +func MyersDiff(old, new string) []Edit { + oldRunes, newRunes := []rune(old), []rune(new) + n, m := len(oldRunes), len(newRunes) + + if n == 0 && m == 0 { + return []Edit{} + } + + // old is empty + if n == 0 { + edits := make([]Edit, m) + for i, r := range newRunes { + edits[i] = Edit{Type: EditInsert, Char: r} + } + return edits + } + + if m == 0 { + edits := make([]Edit, n) + for i, r := range oldRunes { + edits[i] = Edit{Type: EditDelete, Char: r} + } + return edits + } + + max := n + m + v := make([]int, 2*max+1) + var trace [][]int +search: + for d := 0; d <= max; d++ { + // iterate through diagonals + for k := -d; k <= d; k += 2 { + var x int + if k == -d || (k != d && v[max+k-1] < v[max+k+1]) { + x = v[max+k+1] // move down + } else { + x = v[max+k-1] + 1 // move right + } + y := x - k + + // extend the path as far as possible with matching characters + for x < n && y < m && oldRunes[x] == newRunes[y] { + x++ + y++ + } + + v[max+k] = x + + // check if we've reached the end of both strings + if x == n && y == m { + trace = append(trace, append([]int(nil), v...)) + break search + } + } + trace = append(trace, append([]int(nil), v...)) + } + + // backtrack to construct the edit script + edits := make([]Edit, 0, n+m) + x, y := n, m + for d := len(trace) - 1; d >= 0; d-- { + vPrev := trace[d] + k := x - y + var prevK int + if k == -d || (k != d && vPrev[max+k-1] < vPrev[max+k+1]) { + prevK = k + 1 + } else { + prevK = k - 1 + } + prevX := vPrev[max+prevK] + prevY := prevX - prevK + + // add keep edits for matching characters + for x > prevX && y > prevY { + if x > 0 && y > 0 { + edits = append([]Edit{{Type: EditKeep, Char: oldRunes[x-1]}}, edits...) + } + x-- + y-- + } + if y > prevY { + if y > 0 { + edits = append([]Edit{{Type: EditInsert, Char: newRunes[y-1]}}, edits...) + } + y-- + } else if x > prevX { + if x > 0 { + edits = append([]Edit{{Type: EditDelete, Char: oldRunes[x-1]}}, edits...) + } + x-- + } + } + + return edits +} + +// Format converts a slice of Edit operations into a human-readable string representation. +// It groups consecutive edits of the same type and formats them as follows: +// - Unchanged characters are left as-is +// - Inserted characters are wrapped in [+...] +// - Deleted characters are wrapped in [-...] +// +// This function is useful for visualizing the differences between two strings +// in a compact and intuitive format. +// +// Parameters: +// - edits: A slice of Edit operations, typically produced by MyersDiff +// +// Returns: +// - A formatted string representing the diff +// +// Example output: +// +// For the diff between "abcd" and "acbd", the output might be: +// "a[-b]c[+b]d" +// +// Note: +// +// The function assumes that the input slice of edits is in the correct order. +// An empty input slice will result in an empty string. +func Format(edits []Edit) string { + if len(edits) == 0 { + return "" + } + + var ( + result strings.Builder + currentType EditType + currentChars strings.Builder + ) + + flushCurrent := func() { + if currentChars.Len() > 0 { + switch currentType { + case EditKeep: + result.WriteString(currentChars.String()) + case EditInsert: + result.WriteString("[+") + result.WriteString(currentChars.String()) + result.WriteByte(']') + case EditDelete: + result.WriteString("[-") + result.WriteString(currentChars.String()) + result.WriteByte(']') + } + currentChars.Reset() + } + } + + for _, edit := range edits { + if edit.Type != currentType { + flushCurrent() + currentType = edit.Type + } + currentChars.WriteRune(edit.Char) + } + flushCurrent() + + return result.String() +} diff --git a/examples/gno.land/p/demo/diff/diff_test.gno b/examples/gno.land/p/demo/diff/diff_test.gno new file mode 100644 index 00000000000..bbf4fcdf3e0 --- /dev/null +++ b/examples/gno.land/p/demo/diff/diff_test.gno @@ -0,0 +1,182 @@ +package diff + +import ( + "strings" + "testing" +) + +func TestMyersDiff(t *testing.T) { + tests := []struct { + name string + old string + new string + expected string + }{ + { + name: "No difference", + old: "abc", + new: "abc", + expected: "abc", + }, + { + name: "Simple insertion", + old: "ac", + new: "abc", + expected: "a[+b]c", + }, + { + name: "Simple deletion", + old: "abc", + new: "ac", + expected: "a[-b]c", + }, + { + name: "Simple substitution", + old: "abc", + new: "abd", + expected: "ab[-c][+d]", + }, + { + name: "Multiple changes", + old: "The quick brown fox jumps over the lazy dog", + new: "The quick brown cat jumps over the lazy dog", + expected: "The quick brown [-fox][+cat] jumps over the lazy dog", + }, + { + name: "Prefix and suffix", + old: "Hello, world!", + new: "Hello, beautiful world!", + expected: "Hello, [+beautiful ]world!", + }, + { + name: "Complete change", + old: "abcdef", + new: "ghijkl", + expected: "[-abcdef][+ghijkl]", + }, + { + name: "Empty strings", + old: "", + new: "", + expected: "", + }, + { + name: "Old empty", + old: "", + new: "abc", + expected: "[+abc]", + }, + { + name: "New empty", + old: "abc", + new: "", + expected: "[-abc]", + }, + { + name: "non-ascii (Korean characters)", + old: "ASCII 문자가 아닌 것도 되나?", + new: "ASCII 문자가 아닌 것도 됨.", + expected: "ASCII 문자가 아닌 것도 [-되나?][+됨.]", + }, + { + name: "Emoji diff", + old: "Hello 👋 World 🌍", + new: "Hello 👋 Beautiful 🌸 World 🌍", + expected: "Hello 👋 [+Beautiful 🌸 ]World 🌍", + }, + { + name: "Mixed multibyte and ASCII", + old: "こんにちは World", + new: "こんばんは World", + expected: "こん[-にち][+ばん]は World", + }, + { + name: "Chinese characters", + old: "我喜欢编程", + new: "我喜欢看书和编程", + expected: "我喜欢[+看书和]编程", + }, + { + name: "Combining characters", + old: "e\u0301", // é (e + ´) + new: "e\u0300", // è (e + `) + expected: "e[-\u0301][+\u0300]", + }, + { + name: "Right-to-Left languages", + old: "שלום", + new: "שלום עולם", + expected: "שלום[+ עולם]", + }, + { + name: "Normalization NFC and NFD", + old: "e\u0301", // NFD (decomposed) + new: "\u00e9", // NFC (precomposed) + expected: "[-e\u0301][+\u00e9]", + }, + { + name: "Case sensitivity", + old: "abc", + new: "Abc", + expected: "[-a][+A]bc", + }, + { + name: "Surrogate pairs", + old: "Hello 🌍", + new: "Hello 🌎", + expected: "Hello [-🌍][+🌎]", + }, + { + name: "Control characters", + old: "Line1\nLine2", + new: "Line1\r\nLine2", + expected: "Line1[+\r]\nLine2", + }, + { + name: "Mixed scripts", + old: "Hello नमस्ते こんにちは", + new: "Hello สวัสดี こんにちは", + expected: "Hello [-नमस्ते][+สวัสดี] こんにちは", + }, + { + name: "Unicode normalization", + old: "é", // U+00E9 (precomposed) + new: "e\u0301", // U+0065 U+0301 (decomposed) + expected: "[-é][+e\u0301]", + }, + { + name: "Directional marks", + old: "Hello\u200Eworld", // LTR mark + new: "Hello\u200Fworld", // RTL mark + expected: "Hello[-\u200E][+\u200F]world", + }, + { + name: "Zero-width characters", + old: "ab\u200Bc", // Zero-width space + new: "abc", + expected: "ab[-\u200B]c", + }, + { + name: "Worst-case scenario (completely different strings)", + old: strings.Repeat("a", 1000), + 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), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + diff := MyersDiff(tc.old, tc.new) + result := Format(diff) + if result != tc.expected { + t.Errorf("Expected: %s, got: %s", tc.expected, result) + } + }) + } +} diff --git a/examples/gno.land/p/demo/diff/gno.mod b/examples/gno.land/p/demo/diff/gno.mod new file mode 100644 index 00000000000..3041b5f62f1 --- /dev/null +++ b/examples/gno.land/p/demo/diff/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/diff diff --git a/examples/gno.land/p/demo/flow/LICENSE.md b/examples/gno.land/p/demo/flow/LICENSE similarity index 100% rename from examples/gno.land/p/demo/flow/LICENSE.md rename to examples/gno.land/p/demo/flow/LICENSE diff --git a/examples/gno.land/p/demo/gnorkle/agent/gno.mod b/examples/gno.land/p/demo/gnorkle/agent/gno.mod index 6cf359dcf87..093ca9cf38e 100644 --- a/examples/gno.land/p/demo/gnorkle/agent/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/agent/gno.mod @@ -1,3 +1,6 @@ module gno.land/p/demo/gnorkle/agent -require gno.land/p/demo/avl v0.0.0-latest +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno b/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno index 8c306a24a93..40c44b92d0b 100644 --- a/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno +++ b/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno @@ -4,44 +4,25 @@ import ( "testing" "gno.land/p/demo/gnorkle/agent" + "gno.land/p/demo/uassert" ) func TestWhitelist(t *testing.T) { var whitelist agent.Whitelist - if whitelist.HasDefinition() { - t.Error("whitelist should not be defined initially") - } + uassert.False(t, whitelist.HasDefinition(), "whitelist should not be defined initially") whitelist.AddAddresses([]string{"a", "b"}) - if !whitelist.HasAddress("a") { - t.Error(`whitelist should have address "a"`) - } - if !whitelist.HasAddress("b") { - t.Error(`whitelist should have address "b"`) - } - - if !whitelist.HasDefinition() { - t.Error("whitelist should be defined after adding addresses") - } + uassert.True(t, whitelist.HasAddress("a"), `whitelist should have address "a"`) + uassert.True(t, whitelist.HasAddress("b"), `whitelist should have address "b"`) + uassert.True(t, whitelist.HasDefinition(), "whitelist should be defined after adding addresses") whitelist.RemoveAddress("a") - if whitelist.HasAddress("a") { - t.Error(`whitelist should not have address "a"`) - } - if !whitelist.HasAddress("b") { - t.Error(`whitelist should still have address "b"`) - } + uassert.False(t, whitelist.HasAddress("a"), `whitelist should not have address "a"`) + uassert.True(t, whitelist.HasAddress("b"), `whitelist should still have address "b"`) whitelist.ClearAddresses() - if whitelist.HasAddress("a") { - t.Error(`whitelist cleared; should not have address "a"`) - } - if whitelist.HasAddress("b") { - t.Error(`whitelist cleared; should still have address "b"`) - } - - if whitelist.HasDefinition() { - t.Error("whitelist cleared; should not be defined") - } + uassert.False(t, whitelist.HasAddress("a"), `whitelist cleared; should not have address "a"`) + uassert.False(t, whitelist.HasAddress("b"), `whitelist cleared; should still have address "b"`) + uassert.False(t, whitelist.HasDefinition(), "whitelist cleared; should not be defined") } diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno index af8522b5aac..98346ab4e5b 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno @@ -10,6 +10,8 @@ import ( "gno.land/p/demo/gnorkle/ingester" "gno.land/p/demo/gnorkle/message" "gno.land/p/demo/gnorkle/storage/simple" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" ) type mockIngester struct { @@ -44,20 +46,15 @@ func (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress stri func TestNewSingleValueFeed(t *testing.T) { staticFeed := static.NewSingleValueFeed("1", "") - if staticFeed.ID() != "1" { - t.Errorf("expected ID to be 1, got %s", staticFeed.ID()) - } - if staticFeed.Type() != feed.TypeStatic { - t.Errorf("expected static feed type, got %s", staticFeed.Type()) - } + uassert.Equal(t, "1", staticFeed.ID()) + uassert.Equal(t, int(feed.TypeStatic), int(staticFeed.Type())) } func TestFeed_Ingest(t *testing.T) { var undefinedFeed *static.Feed - if undefinedFeed.Ingest("", "", "") != feed.ErrUndefined { - t.Errorf("expected ErrUndefined, got nil") - } + err := undefinedFeed.Ingest("", "", "") + uassert.ErrorIs(t, err, feed.ErrUndefined) tests := []struct { name string @@ -157,15 +154,11 @@ func TestFeed_Ingest(t *testing.T) { errText = err.Error() } - if errText != tt.expErrText { - t.Fatalf("expected error text %s, got %s", tt.expErrText, errText) - } + urequire.Equal(t, tt.expErrText, errText) if tt.doCommit { err := staticFeed.Ingest(message.FuncTypeCommit, "", "") - if err != nil { - t.Fatalf("follow up commit failed: %s", err.Error()) - } + urequire.NoError(t, err, "follow up commit failed") } if tt.verifyIsLocked { @@ -174,31 +167,16 @@ func TestFeed_Ingest(t *testing.T) { errText = err.Error() } - if errText != "feed locked" { - t.Fatalf("expected error text feed locked, got %s", errText) - } + urequire.Equal(t, "feed locked", errText) } - if tt.ingester.providerAddress != tt.providerAddress { - t.Errorf("expected provider address %s, got %s", tt.providerAddress, tt.ingester.providerAddress) - } + uassert.Equal(t, tt.providerAddress, tt.ingester.providerAddress) feedValue, dataType, isLocked := staticFeed.Value() - if feedValue.String != tt.expFeedValueString { - t.Errorf("expected feed value string %s, got %s", tt.expFeedValueString, feedValue.String) - } - - if dataType != "string" { - t.Errorf("expected data type string, got %s", dataType) - } - - if isLocked != tt.verifyIsLocked { - t.Errorf("expected is locked %t, got %t", tt.verifyIsLocked, isLocked) - } - - if staticFeed.IsActive() != tt.expIsActive { - t.Errorf("expected is active %t, got %t", tt.expIsActive, staticFeed.IsActive()) - } + uassert.Equal(t, tt.expFeedValueString, feedValue.String) + uassert.Equal(t, "string", dataType) + uassert.Equal(t, tt.verifyIsLocked, isLocked) + uassert.Equal(t, tt.expIsActive, staticFeed.IsActive()) }) } } @@ -262,9 +240,7 @@ func TestFeed_Tasks(t *testing.T) { tt.tasks..., ) - if len(staticFeed.Tasks()) != len(tt.tasks) { - t.Fatalf("expected %d tasks, got %d", len(tt.tasks), len(staticFeed.Tasks())) - } + urequire.Equal(t, len(tt.tasks), len(staticFeed.Tasks())) var errText string json, err := staticFeed.MarshalJSON() @@ -272,13 +248,8 @@ func TestFeed_Tasks(t *testing.T) { errText = err.Error() } - if errText != tt.expErrText { - t.Fatalf("expected error text %s, got %s", tt.expErrText, errText) - } - - if string(json) != tt.expJSON { - t.Errorf("expected json %s, got %s", tt.expJSON, string(json)) - } + urequire.Equal(t, tt.expErrText, errText) + urequire.Equal(t, tt.expJSON, string(json)) }) } } diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod index 64d5570878b..c651c62cb1b 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod @@ -7,5 +7,7 @@ require ( gno.land/p/demo/gnorkle/ingesters/single v0.0.0-latest gno.land/p/demo/gnorkle/message v0.0.0-latest gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod index d5ab52411d9..71120966a0c 100644 --- a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod @@ -4,4 +4,5 @@ require ( gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest gno.land/p/demo/gnorkle/ingester v0.0.0-latest gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno index b394fbdcc2f..3835e20e690 100644 --- a/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno @@ -6,38 +6,31 @@ import ( "gno.land/p/demo/gnorkle/ingester" "gno.land/p/demo/gnorkle/ingesters/single" "gno.land/p/demo/gnorkle/storage/simple" + "gno.land/p/demo/uassert" ) func TestValueIngester(t *testing.T) { storage := simple.NewStorage(1) var undefinedIngester *single.ValueIngester - if _, err := undefinedIngester.Ingest("asdf", "gno11111"); err != ingester.ErrUndefined { - t.Error("undefined ingester call to Ingest should return ingester.ErrUndefined") - } - if err := undefinedIngester.CommitValue(storage, "gno11111"); err != ingester.ErrUndefined { - t.Error("undefined ingester call to CommitValue should return ingester.ErrUndefined") - } + _, err := undefinedIngester.Ingest("asdf", "gno11111") + uassert.ErrorIs(t, err, ingester.ErrUndefined, "undefined ingester call to Ingest should return ingester.ErrUndefined") + + err = undefinedIngester.CommitValue(storage, "gno11111") + uassert.ErrorIs(t, err, ingester.ErrUndefined, "undefined ingester call to CommitValue should return ingester.ErrUndefined") var valueIngester single.ValueIngester - if typ := valueIngester.Type(); typ != ingester.TypeSingle { - t.Error("single value ingester should return type ingester.TypeSingle") - } + typ := valueIngester.Type() + uassert.Equal(t, int(ingester.TypeSingle), int(typ), "single value ingester should return type ingester.TypeSingle") ingestValue := "value" autocommit, err := valueIngester.Ingest(ingestValue, "gno11111") - if !autocommit { - t.Error("single value ingester should return autocommit true") - } - if err != nil { - t.Errorf("unexpected ingest error %s", err.Error()) - } - - if err := valueIngester.CommitValue(storage, "gno11111"); err != nil { - t.Errorf("unexpected commit error %s", err.Error()) - } - - if latestValue := storage.GetLatest(); latestValue.String != ingestValue { - t.Errorf("expected latest value of %s, got %s", ingestValue, latestValue.String) - } + uassert.True(t, autocommit, "single value ingester should return autocommit true") + uassert.NoError(t, err) + + err = valueIngester.CommitValue(storage, "gno11111") + uassert.NoError(t, err) + + latestValue := storage.GetLatest() + uassert.Equal(t, ingestValue, latestValue.String) } diff --git a/examples/gno.land/p/demo/gnorkle/message/gno.mod b/examples/gno.land/p/demo/gnorkle/message/gno.mod index 5544d0eb873..4baad40ef86 100644 --- a/examples/gno.land/p/demo/gnorkle/message/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/message/gno.mod @@ -1 +1,3 @@ module gno.land/p/demo/gnorkle/message + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/message/parse_test.gno b/examples/gno.land/p/demo/gnorkle/message/parse_test.gno index 7e1c66c5182..eab60c1088c 100644 --- a/examples/gno.land/p/demo/gnorkle/message/parse_test.gno +++ b/examples/gno.land/p/demo/gnorkle/message/parse_test.gno @@ -4,6 +4,7 @@ import ( "testing" "gno.land/p/demo/gnorkle/message" + "gno.land/p/demo/uassert" ) func TestParseFunc(t *testing.T) { @@ -38,13 +39,9 @@ func TestParseFunc(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { funcType, remainder := message.ParseFunc(tt.input) - if funcType != tt.expFuncType { - t.Errorf("expected func type %s, got %s", tt.expFuncType, funcType) - } - if remainder != tt.expRemainder { - t.Errorf("expected remainder of %s, got %s", tt.expRemainder, remainder) - } + uassert.Equal(t, string(tt.expFuncType), string(funcType)) + uassert.Equal(t, tt.expRemainder, remainder) }) } } diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod index d3b7b8af814..cd673a8771c 100644 --- a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod @@ -3,4 +3,7 @@ module gno.land/p/demo/gnorkle/storage/simple require ( gno.land/p/demo/gnorkle/feed v0.0.0-latest gno.land/p/demo/gnorkle/storage v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno b/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno index 4f0c1c45c40..4d831af6482 100644 --- a/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno @@ -5,13 +5,15 @@ import ( "gno.land/p/demo/gnorkle/storage" "gno.land/p/demo/gnorkle/storage/simple" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" ) func TestStorage(t *testing.T) { var undefinedStorage *simple.Storage - if err := undefinedStorage.Put(""); err != storage.ErrUndefined { - t.Error("expected storage.ErrUndefined on undefined storage") - } + err := undefinedStorage.Put("") + uassert.ErrorIs(t, err, storage.ErrUndefined, "expected storage.ErrUndefined on undefined storage") tests := []struct { name string @@ -48,42 +50,20 @@ func TestStorage(t *testing.T) { t.Run(tt.name, func(t *testing.T) { simpleStorage := simple.NewStorage(2) for _, value := range tt.valuesToPut { - if err := simpleStorage.Put(value); err != nil { - t.Fatal("unexpected error putting value in storage") - } + err := simpleStorage.Put(value) + urequire.NoError(t, err, "unexpected error putting value in storage") } latestValue := simpleStorage.GetLatest() - if latestValue.String != tt.expLatestValueString { - t.Errorf("expected latest value of %s, got %s", tt.expLatestValueString, latestValue.String) - } - - if latestValue.Time.IsZero() != tt.expLatestValueTimeIsZero { - t.Errorf( - "expected latest value time zero value of %t, got %t", - tt.expLatestValueTimeIsZero, - latestValue.Time.IsZero(), - ) - } + uassert.Equal(t, tt.expLatestValueString, latestValue.String) + uassert.Equal(t, tt.expLatestValueTimeIsZero, latestValue.Time.IsZero()) historicalValues := simpleStorage.GetHistory() - if len(historicalValues) != len(tt.expHistoricalValueStrings) { - t.Fatal("historical values length does not match") - } + urequire.Equal(t, len(tt.expHistoricalValueStrings), len(historicalValues), "historical values length does not match") for i, expValue := range tt.expHistoricalValueStrings { - if expValue != historicalValues[i].String { - t.Errorf( - "expected historical value at idx %d to be %s, got %s", - i, - expValue, - historicalValues[i].String, - ) - } - - if historicalValues[i].Time.IsZero() { - t.Errorf("unexpeced zero time for historical value at index %d", i) - } + uassert.Equal(t, historicalValues[i].String, expValue) + urequire.False(t, historicalValues[i].Time.IsZero(), ufmt.Sprintf("unexpeced zero time for historical value at index %d", i)) } }) } diff --git a/examples/gno.land/p/demo/grc/exts/vault/errors.gno b/examples/gno.land/p/demo/grc/exts/vault/errors.gno deleted file mode 100644 index 895fe687b01..00000000000 --- a/examples/gno.land/p/demo/grc/exts/vault/errors.gno +++ /dev/null @@ -1,8 +0,0 @@ -package vault - -import "errors" - -var ( - ErrNoSuchVault = errors.New("no such vault") - ErrTooEarlyToRedeem = errors.New("too early to redeem") -) diff --git a/examples/gno.land/p/demo/grc/exts/vault/gno.mod b/examples/gno.land/p/demo/grc/exts/vault/gno.mod deleted file mode 100644 index 2720bf09d95..00000000000 --- a/examples/gno.land/p/demo/grc/exts/vault/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/grc/exts/vault - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/grc/grc20 v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/grc/exts/vault/vault.gno b/examples/gno.land/p/demo/grc/exts/vault/vault.gno deleted file mode 100644 index 47aa70f483e..00000000000 --- a/examples/gno.land/p/demo/grc/exts/vault/vault.gno +++ /dev/null @@ -1,125 +0,0 @@ -package vault - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/grc/grc20" -) - -// Vault is a GRC20 compatible token with vault features. -type Vault interface { - Deposit(amount uint, recovery std.Address, lockDuration uint) error - Unvault(amount uint) error - Recover(dest std.Address) error - Redeem() error -} - -func New(adminToken *grc20.AdminToken) Vault { - return &impl{ - adminToken: adminToken, - users: avl.Tree{}, - } -} - -type impl struct { - adminToken *grc20.AdminToken - users avl.Tree // std.Address -> userVault -} - -type userVault struct { - // constructor parameters. - recover std.Address - lockDuration uint - - // internal parameters. - owner std.Address - redeemMinHeight int64 - unvaultedAmount uint -} - -func (v *impl) Deposit(amount uint, recover std.Address, lockDuration uint) error { - caller := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - - uv := userVault{ - lockDuration: lockDuration, - redeemMinHeight: 0, // will be set in Unvault. - unvaultedAmount: 0, // will be increased in Unvault, zeroed in Redeem. - owner: caller, - } - - // deposit. - err := v.adminToken.Transfer(caller, pkgAddr, uint64(amount)) - if err != nil { - return err - } - v.users.Set(caller.String(), &uv) - - return nil -} - -func (v *impl) Unvault(amount uint) error { - caller := std.GetOrigCaller() - uv, err := v.getUserVault(caller) - if err != nil { - return err - } - - balance, err := v.adminToken.BalanceOf(caller) - if err != nil { - return err - } - if balance < uint64(amount) { - return grc20.ErrInsufficientBalance - } - - println("AAA1", std.GetHeight(), uv.redeemMinHeight, uv.lockDuration) - uv.redeemMinHeight = std.GetHeight() + int64(uv.lockDuration) - uv.unvaultedAmount += amount - v.users.Set(caller.String(), uv) - println("AAA2", std.GetHeight(), uv.redeemMinHeight, uv.lockDuration) - return nil -} - -func (v *impl) Redeem() error { - pkgAddr := std.GetOrigPkgAddr() - caller := std.GetOrigCaller() - uv, err := v.getUserVault(caller) - if err != nil { - return err - } - - println("AAA3", std.GetHeight(), uv.redeemMinHeight, uv.lockDuration) - if std.GetHeight() < uv.redeemMinHeight { - return ErrTooEarlyToRedeem - } - // TODO: check balance. (should be optional, but let's be sure). - // TODO: check height. - - // transfer token. - err = v.adminToken.Transfer(pkgAddr, caller, uint64(uv.unvaultedAmount)) - if err != nil { - return err - } - - uv.unvaultedAmount = 0 - // TODO: if balance == 0 -> destroy? - return nil -} - -func (v *impl) Recover(dest std.Address) error { - // TODO: assert caller (recovery). - // TODO: trasfertToken. - // TODO: destroy? - return nil -} - -func (v *impl) getUserVault(address std.Address) (*userVault, error) { - uvI, exists := v.users.Get(address.String()) - if !exists { - return nil, ErrNoSuchVault - } - uv := uvI.(*userVault) - return uv, nil -} diff --git a/examples/gno.land/p/demo/grc/exts/vault/vault_filetest.gno b/examples/gno.land/p/demo/grc/exts/vault/vault_filetest.gno deleted file mode 100644 index d888a3b5f93..00000000000 --- a/examples/gno.land/p/demo/grc/exts/vault/vault_filetest.gno +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "std" - "time" - - "gno.land/p/demo/grc/exts/vault" - "gno.land/p/demo/grc/grc20" - "gno.land/p/demo/testutils" - "gno.land/p/demo/ufmt" -) - -func main() { - alice := testutils.TestAddress("alice") - bob := testutils.TestAddress("bob") // recovery request address (cold wallet). - charly := testutils.TestAddress("charly") // recovery dest. - pkgaddr := std.GetOrigPkgAddr() - - // create a fooAdminToken + fooToken (GRC20) pair. - fooAdminToken := grc20.NewAdminToken("Foo", "FOO", 4) - fooAdminToken.Mint(alice, 1000) - fooToken := fooAdminToken.GRC20() - - printBalances := func() { - aliceBalance, _ := fooToken.BalanceOf(alice) - bobBalance, _ := fooToken.BalanceOf(bob) - charlyBalance, _ := fooToken.BalanceOf(charly) - pkgBalance, _ := fooToken.BalanceOf(pkgaddr) - println(ufmt.Sprintf( - "balances: alice=%d, bob=%d, charly=%d, pkg=%d, height=%d", - aliceBalance, bobBalance, charlyBalance, pkgBalance, std.GetHeight(), - )) - } - - // create a vault for fooAdminToken. - v := vault.New(fooAdminToken) - printBalances() - - // alice deposits 300 with an unlock duration of 5 blocks. - std.TestSetOrigCaller(alice) - lockAmount := uint(300) - lockDuration := uint(5) - checkErr(v.Deposit(lockAmount, bob, lockDuration)) - printBalances() - - // alice calls unvault for 200 tokens. - checkErr(v.Unvault(200)) - printBalances() - - // alice waits for few blocks. - std.TestSkipHeights(int64(lockDuration) + 1) - printBalances() - - // alice redeems 200 tokens. - checkErr(v.Redeem()) - printBalances() - - // bob instantly recover everything in the wallet. - std.TestSetOrigCaller(bob) - checkErr(v.Recover(charly)) - printBalances() -} - -func checkErr(err error) { - if err != nil { - panic(err) - } -} - -// Output: -// balances: alice=1000, bob=0, charly=0, pkg=0, height=123 -// balances: alice=700, bob=0, charly=0, pkg=300, height=123 -// AAA1 123 0 5 -// AAA2 123 128 5 -// balances: alice=700, bob=0, charly=0, pkg=300, height=123 -// balances: alice=700, bob=0, charly=0, pkg=300, height=129 -// AAA3 129 128 5 -// balances: alice=900, bob=0, charly=0, pkg=100, height=129 -// balances: alice=900, bob=0, charly=0, pkg=100, height=129 diff --git a/examples/gno.land/p/demo/grc/exts/vault/vault_test.gno b/examples/gno.land/p/demo/grc/exts/vault/vault_test.gno deleted file mode 100644 index c0dc6499300..00000000000 --- a/examples/gno.land/p/demo/grc/exts/vault/vault_test.gno +++ /dev/null @@ -1,3 +0,0 @@ -package vault - -// TODO: unit tests, edge cases. 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 d7abf8e1153..2fef3431b43 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 @@ -1,39 +1,28 @@ package grc1155 import ( - "fmt" "std" "testing" - "gno.land/p/demo/users" + "gno.land/p/demo/uassert" ) const dummyURI = "ipfs://xyz" func TestNewBasicGRC1155Token(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - return t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") } func TestUri(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } - - uri := dummy.Uri() - if uri != dummyURI { - t.Errorf("expected: (%s), got: (%s)", dummyURI, uri) - } + uassert.True(t, dummy != nil, "should not be nil") + uassert.Equal(t, dummyURI, dummy.Uri()) } func TestBalanceOf(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") @@ -42,49 +31,30 @@ func TestBalanceOf(t *testing.T) { tid2 := TokenID("2") balanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") + balanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1) - if err != nil { - t.Errorf("should not result in error") - } - if balanceAddr1OfToken1 != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, balanceAddr1OfToken1) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1OfToken1) dummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100}) dummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20}) balanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") balanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") balanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1) - if err != nil { - t.Errorf("should not result in error") - } - - if balanceAddr1OfToken1 != 10 { - t.Errorf("expected: (%d), got: (%d)", 10, balanceAddr1OfToken1) - } - if balanceAddr1OfToken2 != 100 { - t.Errorf("expected: (%d), got: (%d)", 100, balanceAddr1OfToken2) - } - if balanceAddr2OfToken1 != 20 { - t.Errorf("expected: (%d), got: (%d)", 20, balanceAddr2OfToken1) - } + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(10), balanceAddr1OfToken1) + uassert.Equal(t, uint64(100), balanceAddr1OfToken2) + uassert.Equal(t, uint64(20), balanceAddr2OfToken1) } func TestBalanceOfBatch(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") @@ -93,88 +63,56 @@ func TestBalanceOfBatch(t *testing.T) { tid2 := TokenID("2") balanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2}) - if err != nil { - t.Errorf("should not result in error") - } - - if balanceBatch[0] != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, balanceBatch[0]) - } - if balanceBatch[1] != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, balanceBatch[1]) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceBatch[0]) + uassert.Equal(t, uint64(0), balanceBatch[1]) dummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10}) dummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20}) balanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2}) - if err != nil { - t.Errorf("should not result in error") - } - - if balanceBatch[0] != 10 { - t.Errorf("expected: (%d), got: (%d)", 10, balanceBatch[0]) - } - if balanceBatch[1] != 20 { - t.Errorf("expected: (%d), got: (%d)", 20, balanceBatch[1]) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(10), balanceBatch[0]) + uassert.Equal(t, uint64(20), balanceBatch[1]) } func TestIsApprovedForAll(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } + uassert.False(t, isApprovedForAll) } func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.GetOrigCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } + uassert.False(t, isApprovedForAll) err := dummy.SetApprovalForAll(addr, true) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") isApprovedForAll = dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != true { - t.Errorf("expected: (%v), got: (%v)", true, isApprovedForAll) - } + uassert.True(t, isApprovedForAll) err = dummy.SetApprovalForAll(addr, false) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") isApprovedForAll = dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } + uassert.False(t, isApprovedForAll) } func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.GetOrigCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -184,44 +122,28 @@ func TestSafeTransferFrom(t *testing.T) { dummy.mintBatch(caller, []TokenID{tid}, []uint64{100}) err := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeTransferFrom(caller, addr, tid, 160) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeTransferFrom(caller, addr, tid, 60) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check balance of caller after transfer balanceOfCaller, err := dummy.BalanceOf(caller, tid) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfCaller != 40 { - t.Errorf("expected: (%d), got: (%d)", 40, balanceOfCaller) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(40), balanceOfCaller) // Check balance of addr after transfer balanceOfAddr, err := dummy.BalanceOf(addr, tid) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfAddr != 60 { - t.Errorf("expected: (%d), got: (%d)", 60, balanceOfAddr) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(60), balanceOfAddr) } func TestSafeBatchTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.GetOrigCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -232,53 +154,33 @@ func TestSafeBatchTransferFrom(t *testing.T) { dummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100}) err := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60}) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60}) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60}) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") balanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check token1's balance of caller after batch transfer - if balanceBatch[0] != 6 { - t.Errorf("expected: (%d), got: (%d)", 6, balanceBatch[0]) - } + uassert.Equal(t, uint64(6), balanceBatch[0]) // Check token1's balance of addr after batch transfer - if balanceBatch[1] != 4 { - t.Errorf("expected: (%d), got: (%d)", 4, balanceBatch[1]) - } + uassert.Equal(t, uint64(4), balanceBatch[1]) // Check token2's balance of caller after batch transfer - if balanceBatch[2] != 40 { - t.Errorf("expected: (%d), got: (%d)", 40, balanceBatch[2]) - } + uassert.Equal(t, uint64(40), balanceBatch[2]) // Check token2's balance of addr after batch transfer - if balanceBatch[3] != 60 { - t.Errorf("expected: (%d), got: (%d)", 60, balanceBatch[3]) - } + uassert.Equal(t, uint64(60), balanceBatch[3]) } func TestSafeMint(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") @@ -287,45 +189,27 @@ func TestSafeMint(t *testing.T) { tid2 := TokenID("2") err := dummy.SafeMint(zeroAddress, tid1, 100) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeMint(addr1, tid1, 100) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") err = dummy.SafeMint(addr1, tid2, 200) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") err = dummy.SafeMint(addr2, tid1, 50) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") balanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check token1's balance of addr1 after mint - if balanceBatch[0] != 100 { - t.Errorf("expected: (%d), got: (%d)", 100, balanceBatch[0]) - } + uassert.Equal(t, uint64(100), balanceBatch[0]) // Check token1's balance of addr2 after mint - if balanceBatch[1] != 50 { - t.Errorf("expected: (%d), got: (%d)", 50, balanceBatch[1]) - } + uassert.Equal(t, uint64(50), balanceBatch[1]) // Check token2's balance of addr1 after mint - if balanceBatch[2] != 200 { - t.Errorf("expected: (%d), got: (%d)", 200, balanceBatch[2]) - } + uassert.Equal(t, uint64(200), balanceBatch[2]) } func TestSafeBatchMint(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") @@ -334,45 +218,27 @@ func TestSafeBatchMint(t *testing.T) { tid2 := TokenID("2") err := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200}) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") err = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") balanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check token1's balance of addr1 after batch mint - if balanceBatch[0] != 100 { - t.Errorf("expected: (%d), got: (%d)", 100, balanceBatch[0]) - } + uassert.Equal(t, uint64(100), balanceBatch[0]) // Check token1's balance of addr2 after batch mint - if balanceBatch[1] != 300 { - t.Errorf("expected: (%d), got: (%d)", 300, balanceBatch[1]) - } + uassert.Equal(t, uint64(300), balanceBatch[1]) // Check token2's balance of addr1 after batch mint - if balanceBatch[2] != 200 { - t.Errorf("expected: (%d), got: (%d)", 200, balanceBatch[2]) - } + uassert.Equal(t, uint64(200), balanceBatch[2]) // Check token2's balance of addr2 after batch mint - if balanceBatch[3] != 400 { - t.Errorf("expected: (%d), got: (%d)", 400, balanceBatch[3]) - } + uassert.Equal(t, uint64(400), balanceBatch[3]) } func TestBurn(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -380,42 +246,26 @@ func TestBurn(t *testing.T) { tid2 := TokenID("2") dummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200}) - err := dummy.Burn(zeroAddress, tid1, 60) - if err == nil { - t.Errorf("should result in error") - } - err = dummy.Burn(addr, tid1, 160) - if err == nil { - t.Errorf("should result in error") - } - err = dummy.Burn(addr, tid1, 60) - if err != nil { - t.Errorf("should not result in error") - } - err = dummy.Burn(addr, tid2, 60) - if err != nil { - t.Errorf("should not result in error") - } + err := dummy.Burn(zeroAddress, tid1, uint64(60)) + uassert.Error(t, err, "should result in error") + err = dummy.Burn(addr, tid1, uint64(160)) + uassert.Error(t, err, "should result in error") + err = dummy.Burn(addr, tid1, uint64(60)) + uassert.NoError(t, err, "should not result in error") + err = dummy.Burn(addr, tid2, uint64(60)) + uassert.NoError(t, err, "should not result in error") balanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check token1's balance of addr after burn - if balanceBatch[0] != 40 { - t.Errorf("expected: (%d), got: (%d)", 40, balanceBatch[0]) - } + uassert.Equal(t, uint64(40), balanceBatch[0]) // Check token2's balance of addr after burn - if balanceBatch[1] != 140 { - t.Errorf("expected: (%d), got: (%d)", 140, balanceBatch[1]) - } + uassert.Equal(t, uint64(140), balanceBatch[1]) } func TestBatchBurn(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -424,28 +274,16 @@ func TestBatchBurn(t *testing.T) { dummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200}) err := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60}) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60}) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") balanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2}) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check token1's balance of addr after batch burn - if balanceBatch[0] != 40 { - t.Errorf("expected: (%d), got: (%d)", 40, balanceBatch[0]) - } + uassert.Equal(t, uint64(40), balanceBatch[0]) // Check token2's balance of addr after batch burn - if balanceBatch[1] != 140 { - t.Errorf("expected: (%d), got: (%d)", 140, balanceBatch[1]) - } + uassert.Equal(t, uint64(140), balanceBatch[1]) } diff --git a/examples/gno.land/p/demo/grc/grc1155/gno.mod b/examples/gno.land/p/demo/grc/grc1155/gno.mod index b8db3675cf0..d6db0700146 100644 --- a/examples/gno.land/p/demo/grc/grc1155/gno.mod +++ b/examples/gno.land/p/demo/grc/grc1155/gno.mod @@ -2,6 +2,6 @@ module gno.land/p/demo/grc/grc1155 require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc20/admin_token.gno b/examples/gno.land/p/demo/grc/grc20/admin_token.gno deleted file mode 100644 index 83197d940c7..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/admin_token.gno +++ /dev/null @@ -1,266 +0,0 @@ -package grc20 - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/ufmt" -) - -// AdminToken implements a token factory with admin helpers. -// -// Warning: you should not expose this struct to enduser directly. -// -// It allows token administrators to call privileged helpers -// like Mint, Burn, or any Transfer helpers by passing custom owners. -// -// You should initialize your token, then call AdminToken.SafeGRC20() to -// expose a safe instance to the endusers. -type AdminToken struct { - name string - symbol string - decimals uint - totalSupply uint64 - balances avl.Tree // std.Address(owner) -> uint64 - allowances avl.Tree // string(owner+":"+spender) -> uint64 -} - -// safeToken implements the IGRC20 interface. -// -// It is generated by AdminToken.SafeGRC20(). -// It can safely be explosed publicly. -type safeToken struct { - IGRC20 // implements the GRC20 interface. - - factory *AdminToken -} - -func NewAdminToken(name, symbol string, decimals uint) *AdminToken { - // FIXME: check for limits - - return &AdminToken{ - name: name, - symbol: symbol, - decimals: decimals, - - balances: avl.Tree{}, - allowances: avl.Tree{}, - } -} - -func (t *AdminToken) GetName() string { return t.name } -func (t *AdminToken) GetSymbol() string { return t.symbol } -func (t *AdminToken) GetDecimals() uint { return t.decimals } -func (t *AdminToken) TotalSupply() uint64 { return t.totalSupply } - -func (t *AdminToken) BalanceOf(owner std.Address) (uint64, error) { - return t.balanceOf(owner) -} - -func (t *AdminToken) Transfer(owner, to std.Address, amount uint64) error { - return t.transfer(owner, to, amount) -} - -func (t *AdminToken) Allowance(owner, spender std.Address) (uint64, error) { - return t.allowance(owner, spender) -} - -func (t *AdminToken) Approve(owner, spender std.Address, amount uint64) error { - return t.approve(owner, spender, amount) -} - -func (t *AdminToken) TransferFrom(spender, from, to std.Address, amount uint64) error { - if err := t.spendAllowance(from, spender, amount); err != nil { - return err - } - return t.transfer(from, to, amount) -} - -// Administration helpers implementation. -// - -func (t *AdminToken) Mint(to std.Address, amount uint64) error { - return t.mint(to, amount) -} - -func (t *AdminToken) Burn(from std.Address, amount uint64) error { - return t.burn(from, amount) -} - -// private helpers -// - -func (t *AdminToken) mint(address std.Address, amount uint64) error { - if err := checkIsValidAddress(address); err != nil { - return err - } - - // TODO: check for overflow - - t.totalSupply += amount - currentBalance, err := t.balanceOf(address) - if err != nil { - return err - } - newBalance := currentBalance + amount - - t.balances.Set(string(address), newBalance) - - event := TransferEvent{zeroAddress, address, amount} - emit(&event) - - return nil -} - -func (t *AdminToken) burn(address std.Address, amount uint64) error { - if err := checkIsValidAddress(address); err != nil { - return err - } - // TODO: check for overflow - - currentBalance, err := t.balanceOf(address) - if err != nil { - return err - } - if currentBalance < amount { - return ErrInsufficientBalance - } - - t.totalSupply -= amount - newBalance := currentBalance - amount - - t.balances.Set(string(address), newBalance) - - event := TransferEvent{address, zeroAddress, amount} - emit(&event) - - return nil -} - -func (t *AdminToken) balanceOf(address std.Address) (uint64, error) { - if err := checkIsValidAddress(address); err != nil { - return 0, err - } - - balance, found := t.balances.Get(address.String()) - if !found { - return 0, nil - } - return balance.(uint64), nil -} - -func (t *AdminToken) spendAllowance(owner, spender std.Address, amount uint64) error { - if err := checkIsValidAddress(owner); err != nil { - return err - } - if err := checkIsValidAddress(spender); err != nil { - return err - } - - currentAllowance, err := t.allowance(owner, spender) - if err != nil { - return err - } - if currentAllowance < amount { - return ErrInsufficientAllowance - } - - key := allowanceKey(owner, spender) - if currentAllowance > amount { - t.allowances.Set(key, currentAllowance-amount) - } else { - t.allowances.Remove(key) - } - - return nil -} - -func (t *AdminToken) transfer(from, to std.Address, amount uint64) error { - if err := checkIsValidAddress(from); err != nil { - return err - } - if err := checkIsValidAddress(to); err != nil { - return err - } - - if from == to { - return ErrCannotTransferToSelf - } - - toBalance, err := t.balanceOf(to) - if err != nil { - return err - } - fromBalance, err := t.balanceOf(from) - if err != nil { - return err - } - - // debug. - // println("from", from, "to", to, "amount", amount, "fromBalance", fromBalance, "toBalance", toBalance) - - if fromBalance < amount { - return ErrInsufficientBalance - } - - newToBalance := toBalance + amount - newFromBalance := fromBalance - amount - - t.balances.Set(string(to), newToBalance) - t.balances.Set(string(from), newFromBalance) - - event := TransferEvent{from, to, amount} - emit(&event) - - return nil -} - -func (t *AdminToken) allowance(owner, spender std.Address) (uint64, error) { - if err := checkIsValidAddress(owner); err != nil { - return 0, err - } - if err := checkIsValidAddress(spender); err != nil { - return 0, err - } - - allowance, found := t.allowances.Get(allowanceKey(owner, spender)) - if !found { - return 0, nil - } - - return allowance.(uint64), nil -} - -func (t *AdminToken) approve(owner, spender std.Address, amount uint64) error { - if err := checkIsValidAddress(owner); err != nil { - return err - } - if err := checkIsValidAddress(spender); err != nil { - return err - } - - t.allowances.Set(allowanceKey(owner, spender), amount) - - event := ApprovalEvent{owner, spender, amount} - emit(&event) - - return nil -} - -func allowanceKey(owner, spender std.Address) string { - return owner.String() + ":" + spender.String() -} - -func (t *AdminToken) RenderHome() string { - str := "" - str += ufmt.Sprintf("# %s ($%s)\n\n", t.name, t.symbol) - str += ufmt.Sprintf("* **Decimals**: %d\n", t.decimals) - str += ufmt.Sprintf("* **Total supply**: %d\n", t.totalSupply) - str += ufmt.Sprintf("* **Known accounts**: %d\n", t.balances.Size()) - return str -} - -// GRC20 returns an instance that can be exposed to the end user. -func (t *AdminToken) GRC20() IGRC20 { - return &userToken{admin: t} -} diff --git a/examples/gno.land/p/demo/grc/grc20/admin_token_test.gno b/examples/gno.land/p/demo/grc/grc20/admin_token_test.gno deleted file mode 100644 index ea872f4da79..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/admin_token_test.gno +++ /dev/null @@ -1,64 +0,0 @@ -package grc20 - -import ( - "std" - "testing" -) - -func TestAdminTokenImpl(t *testing.T) { - dummy := NewAdminToken("Dummy", "DUMMY", 4) - if dummy == nil { - t.Errorf("should not be nil") - } -} - -func TestAllowance(t *testing.T) { - owner := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - spender := std.Address("g1us8428u2a5satrlxzagsqa5m6vmuze027sxc8x") - dest := std.Address("g1us8428m6a5satrlxzagsqa5m6vmuze02tyelwj") - - token := NewAdminToken("Dummy", "DUMMY", 6) - assertE(t, token.Mint(owner, 100000000)) - assertE(t, token.Approve(owner, spender, 5000000)) - - err := token.TransferFrom(spender, owner, dest, 10000000) - assert(t, err != nil, "should not be able to transfer more than approved") - - tests := []struct { - spend uint64 - exp uint64 - }{ - {3, 4999997}, - {999997, 4000000}, - {4000000, 0}, - } - - for _, tt := range tests { - b0, _ := token.BalanceOf(dest) - assertE(t, token.TransferFrom(spender, owner, dest, tt.spend)) - a, _ := token.Allowance(owner, spender) - assert(t, a == tt.exp, "allowance exp: %d, got %d", tt.exp, a) - - b, _ := token.BalanceOf(dest) - expB := b0 + tt.spend - assert(t, b == expB, "balance exp: %d, got %d", expB, b) - } - - err = token.TransferFrom(spender, owner, dest, 1) - assert(t, err != nil, "no allowance") - - key := allowanceKey(owner, spender) - assert(t, !token.allowances.Has(key), "allowance should be removed") -} - -func assertE(t *testing.T, err error) { - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } -} - -func assert(t *testing.T, cond bool, format string, args ...interface{}) { - if !cond { - t.Fatalf(format, args...) - } -} diff --git a/examples/gno.land/p/demo/grc/grc20/banker.gno b/examples/gno.land/p/demo/grc/grc20/banker.gno new file mode 100644 index 00000000000..f643d3e2635 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc20/banker.gno @@ -0,0 +1,217 @@ +package grc20 + +import ( + "std" + "strconv" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +// Banker implements a token banker with admin privileges. +// +// The Banker is intended to be used in two main ways: +// 1. as a temporary object used to make the initial minting, then deleted. +// 2. preserved in an unexported variable to support conditional administrative +// tasks protected by the contract. +type Banker struct { + name string + symbol string + decimals uint + totalSupply uint64 + balances avl.Tree // std.Address(owner) -> uint64 + allowances avl.Tree // string(owner+":"+spender) -> uint64 + token *token // to share the same pointer +} + +func NewBanker(name, symbol string, decimals uint) *Banker { + if name == "" { + panic("name should not be empty") + } + if symbol == "" { + panic("symbol should not be empty") + } + // XXX additional checks (length, characters, limits, etc) + + b := Banker{ + name: name, + symbol: symbol, + decimals: decimals, + } + t := &token{banker: &b} + b.token = t + return &b +} + +func (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation. +func (b Banker) GetName() string { return b.name } +func (b Banker) GetSymbol() string { return b.symbol } +func (b Banker) GetDecimals() uint { return b.decimals } +func (b Banker) TotalSupply() uint64 { return b.totalSupply } +func (b Banker) KnownAccounts() int { return b.balances.Size() } + +func (b *Banker) Mint(address std.Address, amount uint64) error { + if !address.IsValid() { + return ErrInvalidAddress + } + + // TODO: check for overflow + + b.totalSupply += amount + currentBalance := b.BalanceOf(address) + newBalance := currentBalance + amount + + b.balances.Set(string(address), newBalance) + + std.Emit( + TransferEvent, + "from", "", + "to", string(address), + "value", strconv.Itoa(int(amount)), + ) + + return nil +} + +func (b *Banker) Burn(address std.Address, amount uint64) error { + if !address.IsValid() { + return ErrInvalidAddress + } + // TODO: check for overflow + + currentBalance := b.BalanceOf(address) + if currentBalance < amount { + return ErrInsufficientBalance + } + + b.totalSupply -= amount + newBalance := currentBalance - amount + + b.balances.Set(string(address), newBalance) + + std.Emit( + TransferEvent, + "from", string(address), + "to", "", + "value", strconv.Itoa(int(amount)), + ) + + return nil +} + +func (b Banker) BalanceOf(address std.Address) uint64 { + balance, found := b.balances.Get(address.String()) + if !found { + return 0 + } + return balance.(uint64) +} + +func (b *Banker) SpendAllowance(owner, spender std.Address, amount uint64) error { + if !owner.IsValid() { + return ErrInvalidAddress + } + if !spender.IsValid() { + return ErrInvalidAddress + } + + currentAllowance := b.Allowance(owner, spender) + if currentAllowance < amount { + return ErrInsufficientAllowance + } + + key := allowanceKey(owner, spender) + newAllowance := currentAllowance - amount + + if newAllowance == 0 { + b.allowances.Remove(key) + } else { + b.allowances.Set(key, newAllowance) + } + + return nil +} + +func (b *Banker) Transfer(from, to std.Address, amount uint64) error { + if !from.IsValid() { + return ErrInvalidAddress + } + if !to.IsValid() { + return ErrInvalidAddress + } + if from == to { + return ErrCannotTransferToSelf + } + + toBalance := b.BalanceOf(to) + fromBalance := b.BalanceOf(from) + + // debug. + // println("from", from, "to", to, "amount", amount, "fromBalance", fromBalance, "toBalance", toBalance) + + if fromBalance < amount { + return ErrInsufficientBalance + } + + newToBalance := toBalance + amount + newFromBalance := fromBalance - amount + + b.balances.Set(string(to), newToBalance) + b.balances.Set(string(from), newFromBalance) + + std.Emit( + TransferEvent, + "from", from.String(), + "to", to.String(), + "value", strconv.Itoa(int(amount)), + ) + return nil +} + +func (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error { + if err := b.SpendAllowance(from, spender, amount); err != nil { + return err + } + return b.Transfer(from, to, amount) +} + +func (b *Banker) Allowance(owner, spender std.Address) uint64 { + allowance, found := b.allowances.Get(allowanceKey(owner, spender)) + if !found { + return 0 + } + return allowance.(uint64) +} + +func (b *Banker) Approve(owner, spender std.Address, amount uint64) error { + if !owner.IsValid() { + return ErrInvalidAddress + } + if !spender.IsValid() { + return ErrInvalidAddress + } + + b.allowances.Set(allowanceKey(owner, spender), amount) + + std.Emit( + ApprovalEvent, + "owner", string(owner), + "spender", string(spender), + "value", strconv.Itoa(int(amount)), + ) + + return nil +} + +func (b *Banker) RenderHome() string { + str := "" + str += ufmt.Sprintf("# %s ($%s)\n\n", b.name, b.symbol) + str += ufmt.Sprintf("* **Decimals**: %d\n", b.decimals) + str += ufmt.Sprintf("* **Total supply**: %d\n", b.totalSupply) + str += ufmt.Sprintf("* **Known accounts**: %d\n", b.KnownAccounts()) + return str +} + +func allowanceKey(owner, spender std.Address) string { + return owner.String() + ":" + spender.String() +} diff --git a/examples/gno.land/p/demo/grc/grc20/banker_test.gno b/examples/gno.land/p/demo/grc/grc20/banker_test.gno new file mode 100644 index 00000000000..00a1e75df1f --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc20/banker_test.gno @@ -0,0 +1,51 @@ +package grc20 + +import ( + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" +) + +func TestBankerImpl(t *testing.T) { + dummy := NewBanker("Dummy", "DUMMY", 4) + urequire.False(t, dummy == nil, "dummy should not be nil") +} + +func TestAllowance(t *testing.T) { + var ( + owner = testutils.TestAddress("owner") + spender = testutils.TestAddress("spender") + dest = testutils.TestAddress("dest") + ) + + b := NewBanker("Dummy", "DUMMY", 6) + urequire.NoError(t, b.Mint(owner, 100000000)) + urequire.NoError(t, b.Approve(owner, spender, 5000000)) + urequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), "should not be able to transfer more than approved") + + tests := []struct { + spend uint64 + exp uint64 + }{ + {3, 4999997}, + {999997, 4000000}, + {4000000, 0}, + } + + for _, tt := range tests { + b0 := b.BalanceOf(dest) + urequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend)) + a := b.Allowance(owner, spender) + urequire.Equal(t, a, tt.exp, ufmt.Sprintf("allowance exp: %d, got %d", tt.exp, a)) + b := b.BalanceOf(dest) + expB := b0 + tt.spend + urequire.Equal(t, b, expB, ufmt.Sprintf("balance exp: %d, got %d", expB, b)) + } + + urequire.Error(t, b.TransferFrom(spender, owner, dest, 1), "no allowance") + key := allowanceKey(owner, spender) + urequire.False(t, b.allowances.Has(key), "allowance should be removed") + urequire.Equal(t, b.Allowance(owner, spender), uint64(0), "allowance should be 0") +} diff --git a/examples/gno.land/p/demo/grc/grc20/dummy_test.gno b/examples/gno.land/p/demo/grc/grc20/dummy_test.gno deleted file mode 100644 index 52ed7ecde31..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/dummy_test.gno +++ /dev/null @@ -1,32 +0,0 @@ -package grc20 - -import ( - "std" - "testing" -) - -// TODO: test implementing an IMustGRC20 interface. -// TODO: test implementing a custom method that hides its usage of the IGRC20 interface. - -type dummyImpl struct{} - -// FIXME: this should fail. -var _ IGRC20 = (*dummyImpl)(nil) - -func TestInterface(t *testing.T) { - var dummy IGRC20 = &dummyImpl{} -} - -func (impl *dummyImpl) GetName() string { panic("not implemented") } -func (impl *dummyImpl) GetSymbol() string { panic("not implemented") } -func (impl *dummyImpl) GetDecimals() uint { panic("not implemented") } -func (impl *dummyImpl) TotalSupply() uint64 { panic("not implemented") } -func (impl *dummyImpl) BalanceOf(account std.Address) (uint64, error) { panic("not implemented") } -func (impl *dummyImpl) Transfer(to std.Address, amount uint64) error { panic("not implemented") } -func (impl *dummyImpl) Allowance(owner, spender std.Address) (uint64, error) { - panic("not implemented") -} -func (impl *dummyImpl) Approve(spender std.Address, amount uint64) error { panic("not implemented") } -func (impl *dummyImpl) TransferFrom(from, to std.Address, amount uint64) error { - panic("not implemented") -} diff --git a/examples/gno.land/p/demo/grc/grc20/errors.gno b/examples/gno.land/p/demo/grc/grc20/errors.gno deleted file mode 100644 index 68783ef0df9..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/errors.gno +++ /dev/null @@ -1,10 +0,0 @@ -package grc20 - -import "errors" - -var ( - ErrInsufficientBalance = errors.New("insufficient balance") - ErrInsufficientAllowance = errors.New("insufficient allowance") - ErrInvalidAddress = errors.New("invalid address") - ErrCannotTransferToSelf = errors.New("cannot send transfer to self") -) diff --git a/examples/gno.land/p/demo/grc/grc20/gno.mod b/examples/gno.land/p/demo/grc/grc20/gno.mod index fd80766a956..e872d80ec12 100644 --- a/examples/gno.land/p/demo/grc/grc20/gno.mod +++ b/examples/gno.land/p/demo/grc/grc20/gno.mod @@ -3,5 +3,7 @@ module gno.land/p/demo/grc/grc20 require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/grc/exts v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc20/imustgrc20.gno b/examples/gno.land/p/demo/grc/grc20/imustgrc20.gno deleted file mode 100644 index 01623060ae7..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/imustgrc20.gno +++ /dev/null @@ -1,21 +0,0 @@ -package grc20 - -import ( - "std" - - "gno.land/p/demo/grc/exts" -) - -// IMustGRC20 is like IGRC20 but without returned errors. -// -// It will either panic or silently ignore invalid usages, -// depending on the method. -type IMustGRC20 interface { - exts.TokenMetadata - TotalSupply() uint64 - BalanceOf(account std.Address) uint64 - Transfer(to std.Address, amount uint64) - Allowance(owner, spender std.Address) uint64 - Approve(spender std.Address, amount uint64) - TransferFrom(from, to std.Address, amount uint64) -} diff --git a/examples/gno.land/p/demo/grc/grc20/mustgrc20.gno b/examples/gno.land/p/demo/grc/grc20/mustgrc20.gno deleted file mode 100644 index 5e029e8e9b8..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/mustgrc20.gno +++ /dev/null @@ -1,53 +0,0 @@ -package grc20 - -import "std" - -func Mustify(original IGRC20) IMustGRC20 { - return &mustGRC20{original: original} -} - -type mustGRC20 struct { - original IGRC20 -} - -func (t *mustGRC20) GetName() string { return t.original.GetName() } -func (t *mustGRC20) GetSymbol() string { return t.original.GetSymbol() } -func (t *mustGRC20) GetDecimals() uint { return t.original.GetDecimals() } -func (t *mustGRC20) TotalSupply() uint64 { return t.original.TotalSupply() } - -func (t *mustGRC20) BalanceOf(owner std.Address) uint64 { - balance, err := t.original.BalanceOf(owner) - if err != nil { - return 0 - } - return balance -} - -func (t *mustGRC20) Transfer(to std.Address, amount uint64) { - err := t.original.Transfer(to, amount) - if err != nil { - panic(err) - } -} - -func (t *mustGRC20) Allowance(owner, spender std.Address) uint64 { - allowance, err := t.original.Allowance(owner, spender) - if err != nil { - return 0 - } - return allowance -} - -func (t *mustGRC20) Approve(spender std.Address, amount uint64) { - err := t.original.Approve(spender, amount) - if err != nil { - panic(err) - } -} - -func (t *mustGRC20) TransferFrom(from, to std.Address, amount uint64) { - err := t.original.TransferFrom(from, to, amount) - if err != nil { - panic(err) - } -} diff --git a/examples/gno.land/p/demo/grc/grc20/mustgrc20_test.gno b/examples/gno.land/p/demo/grc/grc20/mustgrc20_test.gno deleted file mode 100644 index ea3d6fdaf65..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/mustgrc20_test.gno +++ /dev/null @@ -1,3 +0,0 @@ -package grc20 - -// TODO: unit tests against MustGRC20 interfaces and helpers. diff --git a/examples/gno.land/p/demo/grc/grc20/token.gno b/examples/gno.land/p/demo/grc/grc20/token.gno new file mode 100644 index 00000000000..e13599e90bb --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc20/token.gno @@ -0,0 +1,88 @@ +package grc20 + +import ( + "std" + + "gno.land/p/demo/grc/exts" +) + +// token implements the Token interface. +// +// It is generated with Banker.Token(). +// It can safely be explosed publicly. +type token struct { + banker *Banker +} + +// var _ Token = (*token)(nil) +func (t *token) GetName() string { return t.banker.name } +func (t *token) GetSymbol() string { return t.banker.symbol } +func (t *token) GetDecimals() uint { return t.banker.decimals } +func (t *token) TotalSupply() uint64 { return t.banker.totalSupply } + +func (t *token) BalanceOf(owner std.Address) uint64 { + return t.banker.BalanceOf(owner) +} + +func (t *token) Transfer(to std.Address, amount uint64) error { + caller := std.PrevRealm().Addr() + return t.banker.Transfer(caller, to, amount) +} + +func (t *token) Allowance(owner, spender std.Address) uint64 { + return t.banker.Allowance(owner, spender) +} + +func (t *token) Approve(spender std.Address, amount uint64) error { + caller := std.PrevRealm().Addr() + return t.banker.Approve(caller, spender, amount) +} + +func (t *token) TransferFrom(from, to std.Address, amount uint64) error { + spender := std.PrevRealm().Addr() + if err := t.banker.SpendAllowance(from, spender, amount); err != nil { + return err + } + return t.banker.Transfer(from, to, amount) +} + +type Token2 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 +} diff --git a/examples/gno.land/p/demo/grc/grc20/user_token_test.gno b/examples/gno.land/p/demo/grc/grc20/token_test.gno similarity index 52% rename from examples/gno.land/p/demo/grc/grc20/user_token_test.gno rename to examples/gno.land/p/demo/grc/grc20/token_test.gno index b2a923cec47..713ad734ed8 100644 --- a/examples/gno.land/p/demo/grc/grc20/user_token_test.gno +++ b/examples/gno.land/p/demo/grc/grc20/token_test.gno @@ -3,35 +3,42 @@ package grc20 import ( "std" "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" ) func TestUserTokenImpl(t *testing.T) { - dummyAdmin := NewAdminToken("Dummy", "DUMMY", 4) - dummyUser := dummyAdmin.GRC20() - _ = dummyUser + bank := NewBanker("Dummy", "DUMMY", 4) + tok := bank.Token() + _ = tok } func TestUserApprove(t *testing.T) { - owner := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - spender := std.Address("g1us8428u2a5satrlxzagsqa5m6vmuze027sxc8x") - dest := std.Address("g1us8428m6a5satrlxzagsqa5m6vmuze02tyelwj") + owner := testutils.TestAddress("owner") + spender := testutils.TestAddress("spender") + dest := testutils.TestAddress("dest") - dummyAdmin := NewAdminToken("Dummy", "DUMMY", 6) + bank := NewBanker("Dummy", "DUMMY", 6) + tok := bank.Token() // Set owner as the original caller std.TestSetOrigCaller(owner) // Mint 100000000 tokens for owner - assertE(t, dummyAdmin.Mint(owner, 100000000)) + urequire.NoError(t, bank.Mint(owner, 100000000)) - dummyUser := dummyAdmin.GRC20() // Approve spender to spend 5000000 tokens - assertE(t, dummyUser.Approve(spender, 5000000)) + urequire.NoError(t, tok.Approve(spender, 5000000)) // Set spender as the original caller std.TestSetOrigCaller(spender) // Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance - err := dummyUser.TransferFrom(owner, dest, 10000000) - assert(t, err != nil, "should not be able to transfer more than approved") + urequire.Error(t, + tok.TransferFrom(owner, dest, 10000000), + ErrInsufficientAllowance.Error(), + "should not be able to transfer more than approved", + ) // Define a set of test data with spend amount and expected remaining allowance tests := []struct { @@ -45,22 +52,21 @@ func TestUserApprove(t *testing.T) { // perform transfer operation,and check if allowance and balance are correct for _, tt := range tests { - b0, _ := dummyUser.BalanceOf(dest) + b0 := tok.BalanceOf(dest) // Perform transfer from owner to dest - assertE(t, dummyUser.TransferFrom(owner, dest, tt.spend)) - a, _ := dummyUser.Allowance(owner, spender) + urequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend)) + a := tok.Allowance(owner, spender) // Check if allowance equals expected value - assert(t, a == tt.exp, "allowance exp: %d,got %d", tt.exp, a) + urequire.True(t, a == tt.exp, ufmt.Sprintf("allowance exp: %d,got %d", tt.exp, a)) // Get dest current balance - b, _ := dummyUser.BalanceOf(dest) + b := tok.BalanceOf(dest) // Calculate expected balance ,should be initial balance plus transfer amount expB := b0 + tt.spend // Check if balance equals expected value - assert(t, b == expB, "balance exp: %d,got %d", expB, b) + urequire.True(t, b == expB, ufmt.Sprintf("balance exp: %d,got %d", expB, b)) } // Try to transfer one token from owner to dest ,should fail because no allowance left - err = dummyUser.TransferFrom(owner, dest, 1) - assert(t, err != nil, "no allowance") + urequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), "no allowance") } diff --git a/examples/gno.land/p/demo/grc/grc20/igrc20.gno b/examples/gno.land/p/demo/grc/grc20/types.gno similarity index 72% rename from examples/gno.land/p/demo/grc/grc20/igrc20.gno rename to examples/gno.land/p/demo/grc/grc20/types.gno index 1256ec22086..fe3aef349d9 100644 --- a/examples/gno.land/p/demo/grc/grc20/igrc20.gno +++ b/examples/gno.land/p/demo/grc/grc20/types.gno @@ -1,19 +1,27 @@ package grc20 import ( + "errors" "std" "gno.land/p/demo/grc/exts" ) -type IGRC20 interface { +var ( + ErrInsufficientBalance = errors.New("insufficient balance") + ErrInsufficientAllowance = errors.New("insufficient allowance") + ErrInvalidAddress = errors.New("invalid address") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") +) + +type Token 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, error) + BalanceOf(account std.Address) uint64 // Moves `amount` tokens from the caller's account to `to`. // @@ -25,7 +33,7 @@ type IGRC20 interface { // zero by default. // // This value changes when {approve} or {transferFrom} are called. - Allowance(owner, spender std.Address) (uint64, error) + Allowance(owner, spender std.Address) uint64 // Sets `amount` as the allowance of `spender` over the caller's tokens. // @@ -47,19 +55,7 @@ type IGRC20 interface { TransferFrom(from, to std.Address, amount uint64) error } -// Emitted when `value` tokens are moved from one account (`from`) to another (`to`). -// -// Note that `value` may be zero. -type TransferEvent struct { - From std.Address - To std.Address - Value uint64 -} - -// Emitted when the allowance of a `spender` for an `owner` is set by -// a call to {approve}. `value` is the new allowance. -type ApprovalEvent struct { - Owner std.Address - Spender std.Address - Value uint64 -} +const ( + TransferEvent = "Transfer" + ApprovalEvent = "Approval" +) diff --git a/examples/gno.land/p/demo/grc/grc20/user_token.gno b/examples/gno.land/p/demo/grc/grc20/user_token.gno deleted file mode 100644 index 7bb10c412b2..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/user_token.gno +++ /dev/null @@ -1,49 +0,0 @@ -package grc20 - -import ( - "std" -) - -// userToken implements the IGRC20 interface. -// -// It is generated by userToken.GRC20(). -// It can safely be explosed publicly. -type userToken struct { - IGRC20 // implements the GRC20 interface. - - admin *AdminToken -} - -// IGRC20 implementation. -// - -func (t *userToken) GetName() string { return t.admin.name } -func (t *userToken) GetSymbol() string { return t.admin.symbol } -func (t *userToken) GetDecimals() uint { return t.admin.decimals } -func (t *userToken) TotalSupply() uint64 { return t.admin.totalSupply } - -func (t *userToken) BalanceOf(owner std.Address) (uint64, error) { - return t.admin.balanceOf(owner) -} - -func (t *userToken) Transfer(to std.Address, amount uint64) error { - caller := std.PrevRealm().Addr() - return t.admin.transfer(caller, to, amount) -} - -func (t *userToken) Allowance(owner, spender std.Address) (uint64, error) { - return t.admin.allowance(owner, spender) -} - -func (t *userToken) Approve(spender std.Address, amount uint64) error { - caller := std.PrevRealm().Addr() - return t.admin.approve(caller, spender, amount) -} - -func (t *userToken) TransferFrom(from, to std.Address, amount uint64) error { - spender := std.PrevRealm().Addr() - if err := t.admin.spendAllowance(from, spender, amount); err != nil { - return err - } - return t.admin.transfer(from, to, amount) -} diff --git a/examples/gno.land/p/demo/grc/grc20/util.gno b/examples/gno.land/p/demo/grc/grc20/util.gno deleted file mode 100644 index 2892b036bbd..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/util.gno +++ /dev/null @@ -1,16 +0,0 @@ -package grc20 - -import "std" - -const zeroAddress = std.Address("") - -func checkIsValidAddress(addr std.Address) error { - if !addr.IsValid() { - return ErrInvalidAddress - } - return nil -} - -func emit(event interface{}) { - // TODO: setup a pubsub system here? -} 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 70255c5e9d1..6375b0307a8 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -4,8 +4,7 @@ import ( "std" "testing" - "gno.land/p/demo/testutils" - "gno.land/p/demo/users" + "gno.land/p/demo/uassert" ) var ( @@ -15,184 +14,127 @@ var ( func TestNewBasicNFT(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") } func TestName(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") + name := dummy.Name() - if name != dummyNFTName { - t.Errorf("expected: (%s), got: (%s)", dummyNFTName, name) - } + uassert.Equal(t, dummyNFTName, name) } func TestSymbol(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") + symbol := dummy.Symbol() - if symbol != dummyNFTSymbol { - t.Errorf("expected: (%s), got: (%s)", dummyNFTSymbol, symbol) - } + uassert.Equal(t, dummyNFTSymbol, symbol) } func TestTokenCount(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") count := dummy.TokenCount() - if count != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, count) - } + uassert.Equal(t, uint64(0), count) dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) count = dummy.TokenCount() - if count != 2 { - t.Errorf("expected: (%d), got: (%d)", 2, count) - } + uassert.Equal(t, uint64(2), count) } func TestBalanceOf(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") balanceAddr1, err := dummy.BalanceOf(addr1) - if err != nil { - t.Errorf("should not result in error") - } - if balanceAddr1 != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, balanceAddr1) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) dummy.mint(addr1, TokenID("1")) dummy.mint(addr1, TokenID("2")) dummy.mint(addr2, TokenID("3")) balanceAddr1, err = dummy.BalanceOf(addr1) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") + balanceAddr2, err := dummy.BalanceOf(addr2) - if err != nil { - t.Errorf("should not result in error") - } - - if balanceAddr1 != 2 { - t.Errorf("expected: (%d), got: (%d)", 2, balanceAddr1) - } - if balanceAddr2 != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceAddr2) - } + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) } func TestOwnerOf(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") owner, err := dummy.OwnerOf(TokenID("invalid")) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should not result in error") dummy.mint(addr1, TokenID("1")) dummy.mint(addr2, TokenID("2")) // Checking for token id "1" owner, err = dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr1 { - t.Errorf("expected: (%s), got: (%s)", addr1.String(), owner.String()) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) // Checking for token id "2" owner, err = dummy.OwnerOf(TokenID("2")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr2 { - t.Errorf("expected: (%s), got: (%s)", addr2.String(), owner.String()) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) } func TestIsApprovedForAll(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } + uassert.False(t, isApprovedForAll) } func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.PrevRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } + uassert.False(t, isApprovedForAll) err := dummy.SetApprovalForAll(addr, true) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") isApprovedForAll = dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != true { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } + uassert.True(t, isApprovedForAll) } func TestGetApproved(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") approvedAddr, err := dummy.GetApproved(TokenID("invalid")) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") } func TestApprove(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.PrevRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -200,29 +142,19 @@ func TestApprove(t *testing.T) { dummy.mint(caller, TokenID("1")) _, err := dummy.GetApproved(TokenID("1")) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") err = dummy.Approve(addr, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") approvedAddr, err := dummy.GetApproved(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if approvedAddr != addr { - t.Errorf("expected: (%s), got: (%s)", addr.String(), approvedAddr.String()) - } + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) } func TestTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.PrevRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -231,43 +163,27 @@ func TestTransferFrom(t *testing.T) { dummy.mint(caller, TokenID("2")) err := dummy.TransferFrom(caller, addr, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should result in error") // Check balance of caller after transfer balanceOfCaller, err := dummy.BalanceOf(caller) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfCaller != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfCaller) - } + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) // Check balance of addr after transfer balanceOfAddr, err := dummy.BalanceOf(addr) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfAddr != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfAddr) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) // Check Owner of transferred Token id owner, err := dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr { - t.Errorf("expected: (%s), got: (%s)", addr.String(), owner.String()) - } + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) } func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") caller := std.PrevRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -276,81 +192,51 @@ func TestSafeTransferFrom(t *testing.T) { dummy.mint(caller, TokenID("2")) err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check balance of caller after transfer balanceOfCaller, err := dummy.BalanceOf(caller) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfCaller != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfCaller) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) // Check balance of addr after transfer balanceOfAddr, err := dummy.BalanceOf(addr) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfAddr != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfAddr) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) // Check Owner of transferred Token id owner, err := dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr { - t.Errorf("expected: (%s), got: (%s)", addr.String(), owner.String()) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) } func TestMint(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") err := dummy.Mint(addr1, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") err = dummy.Mint(addr1, TokenID("2")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") err = dummy.Mint(addr2, TokenID("3")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Try minting duplicate token id err = dummy.Mint(addr2, TokenID("1")) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should not result in error") // Check Owner of Token id owner, err := dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr1 { - t.Errorf("expected: (%s), got: (%s)", addr1.String(), owner.String()) - } + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) } func TestBurn(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") @@ -358,22 +244,16 @@ func TestBurn(t *testing.T) { dummy.mint(addr, TokenID("2")) err := dummy.Burn(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } + uassert.NoError(t, err, "should not result in error") // Check Owner of Token id owner, err := dummy.OwnerOf(TokenID("1")) - if err == nil { - t.Errorf("should result in error") - } + uassert.Error(t, err, "should result in error") } func TestSetTokenURI(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") @@ -383,32 +263,21 @@ func TestSetTokenURI(t *testing.T) { dummy.mint(addr1, TokenID("1")) _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) - - if derr != nil { - t.Errorf("Should not result in error ", derr.Error()) - } + uassert.NoError(t, derr, "should not result in error") // Test case: Invalid token ID _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) - if err != ErrInvalidTokenId { - t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) - } + uassert.ErrorIs(t, err, ErrInvalidTokenId) std.TestSetOrigCaller(std.Address(addr2)) // addr2 _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, err) - } + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Test case: Retrieving TokenURI std.TestSetOrigCaller(std.Address(addr1)) // addr1 dummyTokenURI, err := dummy.TokenURI(TokenID("1")) - if err != nil { - t.Errorf("TokenURI error: %v, ", err.Error()) - } - if dummyTokenURI != tokenURI { - t.Errorf("Expected URI %v, got %v", tokenURI, dummyTokenURI) - } + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) } diff --git a/examples/gno.land/p/demo/grc/grc721/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod index 1eada28e4ee..9e1d6f56ffc 100644 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -3,6 +3,6 @@ module gno.land/p/demo/grc/grc721 require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest ) 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 b7ca6932fe1..ad002a7c98e 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 @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/p/demo/users" + "gno.land/p/demo/uassert" ) func TestSetMetadata(t *testing.T) { @@ -50,9 +50,7 @@ func TestSetMetadata(t *testing.T) { }) // Check if there was an error setting metadata - if derr != nil { - t.Errorf("Should not result in error : %s", derr.Error()) - } + uassert.NoError(t, derr, "Should not result in error") // Test case: Invalid token ID err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ @@ -68,9 +66,7 @@ func TestSetMetadata(t *testing.T) { }) // Check if the error returned matches the expected error - if err != ErrInvalidTokenId { - t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) - } + uassert.ErrorIs(t, err, ErrInvalidTokenId) // Set the original caller to addr2 std.TestSetOrigCaller(addr2) // addr2 @@ -89,45 +85,23 @@ func TestSetMetadata(t *testing.T) { }) // Check if the error returned matches the expected error - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) - } + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Set the original caller back to addr1 std.TestSetOrigCaller(addr1) // addr1 // Retrieve metadata for token 1 dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) - if err != nil { - t.Errorf("Metadata error: %s", err.Error()) - } + uassert.NoError(t, err, "Metadata error") // Check if metadata attributes match expected values - if dummyMetadata.Image != image { - t.Errorf("Expected Metadata's image %s, got %s", image, dummyMetadata.Image) - } - if dummyMetadata.ImageData != imageData { - t.Errorf("Expected Metadata's imageData %s, got %s", imageData, dummyMetadata.ImageData) - } - if dummyMetadata.ExternalURL != externalURL { - t.Errorf("Expected Metadata's externalURL %s, got %s", externalURL, dummyMetadata.ExternalURL) - } - if dummyMetadata.Description != description { - t.Errorf("Expected Metadata's description %s, got %s", description, dummyMetadata.Description) - } - if dummyMetadata.Name != name { - t.Errorf("Expected Metadata's name %s, got %s", name, dummyMetadata.Name) - } - if len(dummyMetadata.Attributes) != len(attributes) { - t.Errorf("Expected %d Metadata's attributes, got %d", len(attributes), len(dummyMetadata.Attributes)) - } - if dummyMetadata.BackgroundColor != backgroundColor { - t.Errorf("Expected Metadata's backgroundColor %s, got %s", backgroundColor, dummyMetadata.BackgroundColor) - } - if dummyMetadata.AnimationURL != animationURL { - t.Errorf("Expected Metadata's animationURL %s, got %s", animationURL, dummyMetadata.AnimationURL) - } - if dummyMetadata.YoutubeURL != youtubeURL { - t.Errorf("Expected Metadata's youtubeURL %s, got %s", youtubeURL, dummyMetadata.YoutubeURL) - } + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) } 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 8c7bb33caa5..7893453a1c6 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 @@ -5,15 +5,12 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/p/demo/ufmt" - "gno.land/p/demo/users" + "gno.land/p/demo/uassert" ) func TestSetTokenRoyalty(t *testing.T) { dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } + uassert.True(t, dummy != nil, "should not be nil") addr1 := testutils.TestAddress("alice") addr2 := testutils.TestAddress("bob") @@ -32,19 +29,14 @@ func TestSetTokenRoyalty(t *testing.T) { PaymentAddress: paymentAddress, Percentage: percentage, }) - - if derr != nil { - t.Errorf("Should not result in error : %s", derr.Error()) - } + uassert.NoError(t, derr, "Should not result in error") // Test case: Invalid token ID err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) - if err != ErrInvalidTokenId { - t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) - } + uassert.ErrorIs(t, derr, ErrInvalidTokenId) std.TestSetOrigCaller(addr2) // addr2 @@ -52,42 +44,27 @@ func TestSetTokenRoyalty(t *testing.T) { PaymentAddress: paymentAddress, Percentage: percentage, }) - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) - } + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Test case: Invalid payment address aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ PaymentAddress: std.Address("###"), // invalid address Percentage: percentage, }) - if aerr != ErrInvalidRoyaltyPaymentAddress { - t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPaymentAddress, aerr) - } + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) // Test case: Invalid percentage perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: uint64(200), // over maxRoyaltyPercentage }) - - if perr != ErrInvalidRoyaltyPercentage { - t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPercentage, perr) - } + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) // Test case: Retrieving Royalty Info std.TestSetOrigCaller(addr1) // addr1 dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) - if rerr != nil { - t.Errorf("RoyaltyInfo error: %s", rerr.Error()) - } - - if dummyPaymentAddress != paymentAddress { - t.Errorf("Expected RoyaltyPaymentAddress %s, got %s", paymentAddress, dummyPaymentAddress) - } - - if dummyRoyaltyAmount != expectRoyaltyAmount { - t.Errorf("Expected RoyaltyAmount %d, got %d", expectRoyaltyAmount, dummyRoyaltyAmount) - } + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) } diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno index a8a74ea15cc..c4603d6328e 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -1,8 +1,6 @@ package grc721 -import ( - "std" -) +import "std" // IGRC2981 follows the Ethereum standard type IGRC2981 interface { diff --git a/examples/gno.land/p/demo/groups/groups.gno b/examples/gno.land/p/demo/groups/groups.gno index d7315ac982c..fcf77dd2a74 100644 --- a/examples/gno.land/p/demo/groups/groups.gno +++ b/examples/gno.land/p/demo/groups/groups.gno @@ -1,8 +1,6 @@ package groups -import ( - "gno.land/r/demo/boards" -) +import "gno.land/r/demo/boards" // TODO implement something and test. type Group struct { diff --git a/examples/gno.land/p/demo/groups/z_1_filetest.gno b/examples/gno.land/p/demo/groups/z_1_filetest.gno index 9a423d7a4a0..280b3956ab6 100644 --- a/examples/gno.land/p/demo/groups/z_1_filetest.gno +++ b/examples/gno.land/p/demo/groups/z_1_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test import ( diff --git a/examples/gno.land/p/demo/int256/absolute.gno b/examples/gno.land/p/demo/int256/absolute.gno index 23beb4071a4..825dd60c62a 100644 --- a/examples/gno.land/p/demo/int256/absolute.gno +++ b/examples/gno.land/p/demo/int256/absolute.gno @@ -1,8 +1,6 @@ package int256 -import ( - "gno.land/p/demo/uint256" -) +import "gno.land/p/demo/uint256" // Abs returns |z| func (z *Int) Abs() *uint256.Uint { diff --git a/examples/gno.land/p/demo/int256/arithmetic.gno b/examples/gno.land/p/demo/int256/arithmetic.gno index ccb494bec9a..ce05426f585 100644 --- a/examples/gno.land/p/demo/int256/arithmetic.gno +++ b/examples/gno.land/p/demo/int256/arithmetic.gno @@ -1,8 +1,6 @@ package int256 -import ( - "gno.land/p/demo/uint256" -) +import "gno.land/p/demo/uint256" func (z *Int) Add(x, y *Int) *Int { z.initiateAbs() diff --git a/examples/gno.land/p/demo/int256/bitwise_test.gno b/examples/gno.land/p/demo/int256/bitwise_test.gno index ea51360ea65..8dc16cd17ac 100644 --- a/examples/gno.land/p/demo/int256/bitwise_test.gno +++ b/examples/gno.land/p/demo/int256/bitwise_test.gno @@ -1,11 +1,9 @@ package int256 -import ( - "gno.land/p/demo/uint256" -) - import ( "testing" + + "gno.land/p/demo/uint256" ) func TestOr(t *testing.T) { diff --git a/examples/gno.land/p/demo/int256/cmp_test.gno b/examples/gno.land/p/demo/int256/cmp_test.gno index 155cfa94b92..81b9231babe 100644 --- a/examples/gno.land/p/demo/int256/cmp_test.gno +++ b/examples/gno.land/p/demo/int256/cmp_test.gno @@ -1,8 +1,6 @@ package int256 -import ( - "testing" -) +import "testing" func TestEq(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/int256/conversion.gno b/examples/gno.land/p/demo/int256/conversion.gno index c6955f37421..ee6e7560f15 100644 --- a/examples/gno.land/p/demo/int256/conversion.gno +++ b/examples/gno.land/p/demo/int256/conversion.gno @@ -1,8 +1,6 @@ package int256 -import ( - "gno.land/p/demo/uint256" -) +import "gno.land/p/demo/uint256" // SetInt64 sets z to x and returns z. func (z *Int) SetInt64(x int64) *Int { diff --git a/examples/gno.land/p/demo/int256/int256_test.gno b/examples/gno.land/p/demo/int256/int256_test.gno index 974cc6ed87f..7c8181d1bec 100644 --- a/examples/gno.land/p/demo/int256/int256_test.gno +++ b/examples/gno.land/p/demo/int256/int256_test.gno @@ -1,9 +1,7 @@ // ported from github.com/mempooler/int256 package int256 -import ( - "testing" -) +import "testing" func TestSign(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/json/buffer_test.gno b/examples/gno.land/p/demo/json/buffer_test.gno index a1acce4eba0..b8dce390a61 100644 --- a/examples/gno.land/p/demo/json/buffer_test.gno +++ b/examples/gno.land/p/demo/json/buffer_test.gno @@ -1,8 +1,6 @@ package json -import ( - "testing" -) +import "testing" func TestBufferCurrent(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/json/decode_test.gno b/examples/gno.land/p/demo/json/decode_test.gno index 5a04846224e..8aad07169f2 100644 --- a/examples/gno.land/p/demo/json/decode_test.gno +++ b/examples/gno.land/p/demo/json/decode_test.gno @@ -2,7 +2,6 @@ package json import ( "bytes" - "encoding/json" "testing" ) diff --git a/examples/gno.land/p/demo/json/encode_test.gno b/examples/gno.land/p/demo/json/encode_test.gno index 33a1fae3d4e..e8e53993b5c 100644 --- a/examples/gno.land/p/demo/json/encode_test.gno +++ b/examples/gno.land/p/demo/json/encode_test.gno @@ -1,8 +1,6 @@ package json -import ( - "testing" -) +import "testing" func TestMarshal_Primitive(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/json/parser_test.gno b/examples/gno.land/p/demo/json/parser_test.gno index 44a2fee6404..078aa048a61 100644 --- a/examples/gno.land/p/demo/json/parser_test.gno +++ b/examples/gno.land/p/demo/json/parser_test.gno @@ -1,9 +1,6 @@ package json -import ( - "strconv" - "testing" -) +import "testing" func TestParseStringLiteral(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/json/path_test.gno b/examples/gno.land/p/demo/json/path_test.gno index dd242849f03..f68e3eb679f 100644 --- a/examples/gno.land/p/demo/json/path_test.gno +++ b/examples/gno.land/p/demo/json/path_test.gno @@ -1,8 +1,6 @@ package json -import ( - "testing" -) +import "testing" func TestParseJSONPath(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/math_eval/int32/int32_test.gno b/examples/gno.land/p/demo/math_eval/int32/int32_test.gno index 3e43210c495..a50a91fbd31 100644 --- a/examples/gno.land/p/demo/math_eval/int32/int32_test.gno +++ b/examples/gno.land/p/demo/math_eval/int32/int32_test.gno @@ -1,11 +1,6 @@ package int32 -import ( - "std" - "testing" - - "gno.land/p/demo/ufmt" -) +import "testing" func TestOne(t *testing.T) { ttt := []struct { diff --git a/examples/gno.land/p/demo/memeland/gno.mod b/examples/gno.land/p/demo/memeland/gno.mod index b1409c7db6d..66f22d1ccee 100644 --- a/examples/gno.land/p/demo/memeland/gno.mod +++ b/examples/gno.land/p/demo/memeland/gno.mod @@ -5,5 +5,6 @@ require ( gno.land/p/demo/ownable v0.0.0-latest gno.land/p/demo/seqid v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/memeland/memeland_test.gno b/examples/gno.land/p/demo/memeland/memeland_test.gno index 76c44327993..95065b8cd64 100644 --- a/examples/gno.land/p/demo/memeland/memeland_test.gno +++ b/examples/gno.land/p/demo/memeland/memeland_test.gno @@ -7,15 +7,14 @@ import ( "time" "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" ) func TestPostMeme(t *testing.T) { m := NewMemeland() id := m.PostMeme("Test meme data", time.Now().Unix()) - if id == "" { - t.Error("Expected valid ID, got empty string") - } + uassert.NotEqual(t, "", string(id), "Expected valid ID, got empty string") } func TestGetPostsInRangePagination(t *testing.T) { @@ -56,9 +55,7 @@ func TestGetPostsInRangePagination(t *testing.T) { // Count posts by how many times id: shows up in JSON string postCount := strings.Count(result, `"id":"`) - if postCount != tc.expectedNumOfPosts { - t.Errorf("Expected %d posts in the JSON string, but found %d", tc.expectedNumOfPosts, postCount) - } + uassert.Equal(t, tc.expectedNumOfPosts, postCount) }) } } @@ -91,28 +88,22 @@ func TestGetPostsInRangeByTimestamp(t *testing.T) { "DATE_CREATED", // sort by newest first ) - if jsonStr == "" { - t.Error("Expected non-empty JSON string, got empty string") - } + uassert.NotEmpty(t, jsonStr, "Expected non-empty JSON string, got empty string") // Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering postCount := strings.Count(jsonStr, `"id":"`) - if postCount != m.MemeCounter { - t.Errorf("Expected %d posts in the JSON string, but found %d", m.MemeCounter, postCount) - } + uassert.Equal(t, uint64(m.MemeCounter), uint64(postCount)) // Check if data is there for _, expData := range memeData { - if !strings.Contains(jsonStr, expData) { - t.Errorf("Expected %s in the JSON string, but counld't find it", expData) - } + check := strings.Contains(jsonStr, expData) + uassert.True(t, check, ufmt.Sprintf("Expected %s in the JSON string, but counld't find it", expData)) } // Check if ordering is correct, sort by created date for i := 0; i < len(memeData)-2; i++ { - if strings.Index(jsonStr, memeData[i]) < strings.Index(jsonStr, memeData[i+1]) { - t.Errorf("Expected %s to be before %s, but was at %d, and %d", memeData[i], memeData[i+1], i, i+1) - } + check := strings.Index(jsonStr, memeData[i]) >= strings.Index(jsonStr, memeData[i+1]) + uassert.True(t, check, ufmt.Sprintf("Expected %s to be before %s, but was at %d, and %d", memeData[i], memeData[i+1], i, i+1)) } } @@ -151,20 +142,15 @@ func TestGetPostsInRangeByUpvote(t *testing.T) { "UPVOTES", // sort by upvote ) - if jsonStr == "" { - t.Error("Expected non-empty JSON string, got empty string") - } + uassert.NotEmpty(t, jsonStr, "Expected non-empty JSON string, got empty string") // Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering postCount := strings.Count(jsonStr, `"id":"`) - if postCount != m.MemeCounter { - t.Errorf("Expected %d posts in the JSON string, but found %d", m.MemeCounter, postCount) - } + uassert.Equal(t, uint64(m.MemeCounter), uint64(postCount)) // Check if ordering is correct - if strings.Index(jsonStr, "Meme #1") > strings.Index(jsonStr, "Meme #2") { - t.Errorf("Expected %s to be before %s", memeData1, memeData2) - } + check := strings.Index(jsonStr, "Meme #1") <= strings.Index(jsonStr, "Meme #2") + uassert.True(t, check, ufmt.Sprintf("Expected %s to be before %s", memeData1, memeData2)) } func TestBadSortBy(t *testing.T) { @@ -225,9 +211,7 @@ func TestNoPosts(t *testing.T) { jsonStr := m.GetPostsInRange(0, now, 1, 1, "DATE_CREATED") - if jsonStr != "[]" { - t.Errorf("Expected 0 posts to return [], got %s", jsonStr) - } + uassert.Equal(t, jsonStr, "[]") } func TestUpvote(t *testing.T) { @@ -239,23 +223,15 @@ func TestUpvote(t *testing.T) { // Initial upvote count should be 0 post := m.getPost(postID) - - if post.UpvoteTracker.Size() != 0 { - t.Errorf("Expected initial upvotes to be 0, got %d", post.UpvoteTracker.Size()) - } + uassert.Equal(t, 0, post.UpvoteTracker.Size()) // Upvote the post upvoteResult := m.Upvote(postID) - if upvoteResult != "upvote successful" { - t.Errorf("Expected upvote to be successful, got: %s", upvoteResult) - } + uassert.Equal(t, "upvote successful", upvoteResult) // Retrieve the post again and check the upvote count post = m.getPost(postID) - - if post.UpvoteTracker.Size() != 1 { - t.Errorf("Expected upvotes to be 1 after upvoting, got %d", post.UpvoteTracker.Size()) - } + uassert.Equal(t, 1, post.UpvoteTracker.Size()) } func TestDelete(t *testing.T) { @@ -277,13 +253,8 @@ func TestDelete(t *testing.T) { std.TestSetOrigCaller(alice) id := m.RemovePost(postID) - if id != postID { - t.Errorf("post IDs not matching") - } - - if len(m.Posts) != 0 { - t.Errorf("there should be 0 posts after removing") - } + uassert.Equal(t, postID, id, "post IDs not matching") + uassert.Equal(t, 0, len(m.Posts), "there should be 0 posts after removing") } func TestDeleteByNonAdmin(t *testing.T) { diff --git a/examples/gno.land/p/demo/merkle/merkle_test.gno b/examples/gno.land/p/demo/merkle/merkle_test.gno index 7f1273e1d82..4aa99baa6d0 100644 --- a/examples/gno.land/p/demo/merkle/merkle_test.gno +++ b/examples/gno.land/p/demo/merkle/merkle_test.gno @@ -3,7 +3,6 @@ package merkle import ( "fmt" "testing" - "time" ) type testData struct { diff --git a/examples/gno.land/p/demo/mux/response.gno b/examples/gno.land/p/demo/mux/response.gno index 5b579b1d515..a2a52c7c7aa 100644 --- a/examples/gno.land/p/demo/mux/response.gno +++ b/examples/gno.land/p/demo/mux/response.gno @@ -1,8 +1,6 @@ package mux -import ( - "strings" -) +import "strings" // ResponseWriter represents the response writer. type ResponseWriter struct { diff --git a/examples/gno.land/p/demo/mux/router_test.gno b/examples/gno.land/p/demo/mux/router_test.gno index 71a87b687d3..13fd5b97955 100644 --- a/examples/gno.land/p/demo/mux/router_test.gno +++ b/examples/gno.land/p/demo/mux/router_test.gno @@ -1,9 +1,6 @@ package mux -import ( - "strings" - "testing" -) +import "testing" func TestRouter_Render(t *testing.T) { // Define handlers and route configuration diff --git a/examples/gno.land/p/demo/nestedpkg/gno.mod b/examples/gno.land/p/demo/nestedpkg/gno.mod new file mode 100644 index 00000000000..24e16fdeb74 --- /dev/null +++ b/examples/gno.land/p/demo/nestedpkg/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/nestedpkg diff --git a/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno b/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno new file mode 100644 index 00000000000..4c489f430f9 --- /dev/null +++ b/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno @@ -0,0 +1,89 @@ +// Package nestedpkg provides helpers for package-path based access control. +// It is useful for upgrade patterns relying on namespaces. +package nestedpkg + +// To test this from a realm and have std.CurrentRealm/PrevRealm 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. + +import ( + "std" + "strings" +) + +// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm. +func IsCallerSubPath() bool { + var ( + cur = std.CurrentRealm().PkgPath() + "/" + prev = std.PrevRealm().PkgPath() + "/" + ) + return strings.HasPrefix(prev, cur) +} + +// AssertCallerIsSubPath panics if IsCallerSubPath returns false. +func AssertCallerIsSubPath() { + var ( + cur = std.CurrentRealm().PkgPath() + "/" + prev = std.PrevRealm().PkgPath() + "/" + ) + if !strings.HasPrefix(prev, cur) { + panic("call restricted to nested packages. current realm is " + cur + ", previous realm is " + prev) + } +} + +// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm. +func IsCallerParentPath() bool { + var ( + cur = std.CurrentRealm().PkgPath() + "/" + prev = std.PrevRealm().PkgPath() + "/" + ) + return strings.HasPrefix(cur, prev) +} + +// AssertCallerIsParentPath panics if IsCallerParentPath returns false. +func AssertCallerIsParentPath() { + var ( + cur = std.CurrentRealm().PkgPath() + "/" + prev = std.PrevRealm().PkgPath() + "/" + ) + if !strings.HasPrefix(cur, prev) { + panic("call restricted to parent packages. current realm is " + cur + ", previous realm is " + prev) + } +} + +// IsSameNamespace checks if the caller realm and the current realm are in the same namespace. +func IsSameNamespace() bool { + var ( + cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" + prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" + ) + return cur == prev +} + +// AssertIsSameNamespace panics if IsSameNamespace returns false. +func AssertIsSameNamespace() { + var ( + cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" + prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" + ) + if cur != prev { + panic("call restricted to packages from the same namespace. current realm is " + cur + ", previous realm is " + prev) + } +} + +// nsFromPath extracts the namespace from a package path. +func nsFromPath(pkgpath string) string { + parts := strings.Split(pkgpath, "/") + + // Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/... + // XXX: Consider extra checks. + // XXX: Support non gno.land domains, where p/ and r/ won't be enforced. + if len(parts) >= 3 { + return parts[2] + } + return "" +} + +// XXX: Consider adding IsCallerDirectlySubPath +// XXX: Consider adding IsCallerDirectlyParentPath diff --git a/examples/gno.land/p/demo/ownable/gno.mod b/examples/gno.land/p/demo/ownable/gno.mod index 9a9abb1e661..00f7812f6f5 100644 --- a/examples/gno.land/p/demo/ownable/gno.mod +++ b/examples/gno.land/p/demo/ownable/gno.mod @@ -1 +1,6 @@ module gno.land/p/demo/ownable + +require ( + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index 7f2eac008e1..75ebcde0a28 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -4,6 +4,8 @@ import ( "std" ) +const OwnershipTransferEvent = "OwnershipTransfer" + // Ownable is meant to be used as a top-level object to make your contract ownable OR // being embedded in a Gno object to manage per-object ownership. type Ownable struct { @@ -12,10 +14,14 @@ type Ownable struct { func New() *Ownable { return &Ownable{ - owner: std.GetOrigCaller(), + owner: std.PrevRealm().Addr(), } } +func NewWithAddress(addr std.Address) *Ownable { + return &Ownable{owner: addr} +} + // TransferOwnership transfers ownership of the Ownable struct to a new address func (o *Ownable) TransferOwnership(newOwner std.Address) error { err := o.CallerIsOwner() @@ -27,7 +33,13 @@ func (o *Ownable) TransferOwnership(newOwner std.Address) error { return ErrInvalidAddress } + prevOwner := o.owner o.owner = newOwner + std.Emit( + OwnershipTransferEvent, + "from", string(prevOwner), + "to", string(newOwner), + ) return nil } @@ -40,18 +52,32 @@ func (o *Ownable) DropOwnership() error { return err } + prevOwner := o.owner o.owner = "" + + std.Emit( + OwnershipTransferEvent, + "from", string(prevOwner), + "to", "", + ) + return nil } +func (o Ownable) Owner() std.Address { + return o.owner +} + // CallerIsOwner checks if the caller of the function is the Realm's owner -func (o *Ownable) CallerIsOwner() error { - if std.GetOrigCaller() == o.owner { +func (o Ownable) CallerIsOwner() error { + if std.PrevRealm().Addr() == o.owner { return nil } return ErrUnauthorized } -func (o *Ownable) Owner() std.Address { - return o.owner +func (o Ownable) AssertCallerIsOwner() { + if std.PrevRealm().Addr() != 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 f725795fd47..6217948d587 100644 --- a/examples/gno.land/p/demo/ownable/ownable_test.gno +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -3,111 +3,104 @@ package ownable import ( "std" "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" ) var ( - firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") - secondCaller = std.Address("g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa") + firstCaller = testutils.TestAddress("first") + secondCaller = testutils.TestAddress("second") ) func TestNew(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed - result := New() - if firstCaller != result.owner { - t.Fatalf("Expected %s, got: %s\n", firstCaller, result.owner) - } + o := New() + got := o.Owner() + uassert.Equal(t, firstCaller, got) } -func TestOwner(t *testing.T) { - std.TestSetOrigCaller(firstCaller) +func TestNewWithAddress(t *testing.T) { + o := NewWithAddress(firstCaller) - result := New() - resultOwner := result.Owner() + got := o.Owner() + uassert.Equal(t, firstCaller, got) +} +func TestOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(firstCaller)) + + o := New() expected := firstCaller - if resultOwner != expected { - t.Fatalf("Expected %s, got: %s\n", expected, result) - } + got := o.Owner() + uassert.Equal(t, expected, got) } func TestTransferOwnership(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + o := New() err := o.TransferOwnership(secondCaller) - if err != nil { - t.Fatalf("TransferOwnership failed, %v", err) - } - - result := o.Owner() - if secondCaller != result { - t.Fatalf("Expected: %s, got: %s\n", secondCaller, result) - } + uassert.NoError(t, err, "TransferOwnership failed") + + got := o.Owner() + uassert.Equal(t, secondCaller, got) } func TestCallerIsOwner(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) o := New() unauthorizedCaller := secondCaller - std.TestSetOrigCaller(unauthorizedCaller) + std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) + std.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed err := o.CallerIsOwner() - if err == nil { - t.Fatalf("Expected %s to not be owner\n", unauthorizedCaller) - } + uassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller) } func TestDropOwnership(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) o := New() err := o.DropOwnership() - if err != nil { - t.Fatalf("DropOwnership failed, %v", err) - } + uassert.NoError(t, err, "DropOwnership failed") owner := o.Owner() - if owner != "" { - t.Fatalf("Expected owner to be empty, not %s\n", owner) - } + uassert.Empty(t, owner, "owner should be empty") } // Errors func TestErrUnauthorized(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed o := New() - std.TestSetOrigCaller(secondCaller) + std.TestSetRealm(std.NewUserRealm(secondCaller)) + std.TestSetOrigCaller(secondCaller) // TODO(bug): should not be needed err := o.TransferOwnership(firstCaller) - if err != ErrUnauthorized { - t.Fatalf("Should've been ErrUnauthorized, was %v", err) - } + uassert.ErrorContains(t, err, ErrUnauthorized.Error()) err = o.DropOwnership() - if err != ErrUnauthorized { - t.Fatalf("Should've been ErrUnauthorized, was %v", err) - } + uassert.ErrorContains(t, err, ErrUnauthorized.Error()) } func TestErrInvalidAddress(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) o := New() err := o.TransferOwnership("") - if err != ErrInvalidAddress { - t.Fatalf("Should've been ErrInvalidAddress, was %v", err) - } + uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") - if err != ErrInvalidAddress { - t.Fatalf("Should've been ErrInvalidAddress, was %v", err) - } + uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) } diff --git a/examples/gno.land/p/demo/pausable/gno.mod b/examples/gno.land/p/demo/pausable/gno.mod index 08c7a4f7e5f..156875f7d85 100644 --- a/examples/gno.land/p/demo/pausable/gno.mod +++ b/examples/gno.land/p/demo/pausable/gno.mod @@ -1,3 +1,6 @@ module gno.land/p/demo/pausable -require gno.land/p/demo/ownable v0.0.0-latest +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/pausable/pausable_test.gno b/examples/gno.land/p/demo/pausable/pausable_test.gno index cc95c457573..c9557245bdf 100644 --- a/examples/gno.land/p/demo/pausable/pausable_test.gno +++ b/examples/gno.land/p/demo/pausable/pausable_test.gno @@ -5,6 +5,7 @@ import ( "testing" "gno.land/p/demo/ownable" + "gno.land/p/demo/urequire" ) var ( @@ -17,13 +18,8 @@ func TestNew(t *testing.T) { result := New() - if result.paused != false { - t.Fatalf("Expected result to be unpaused, got %t\n", result.paused) - } - - if result.Owner() != firstCaller { - t.Fatalf("Expected %s, got %s\n", firstCaller, result.Owner()) - } + urequire.False(t, result.paused, "Expected result to be unpaused") + urequire.Equal(t, firstCaller.String(), result.Owner().String()) } func TestNewFromOwnable(t *testing.T) { @@ -33,9 +29,7 @@ func TestNewFromOwnable(t *testing.T) { std.TestSetOrigCaller(secondCaller) result := NewFromOwnable(o) - if result.Owner() != firstCaller { - t.Fatalf("Expected %s, got %s\n", firstCaller, result.Owner()) - } + urequire.Equal(t, firstCaller.String(), result.Owner().String()) } func TestSetUnpaused(t *testing.T) { @@ -44,9 +38,7 @@ func TestSetUnpaused(t *testing.T) { result := New() result.Unpause() - if result.IsPaused() { - t.Fatalf("Expected result to be unpaused, got %t\n", result.IsPaused()) - } + urequire.False(t, result.IsPaused(), "Expected result to be unpaused") } func TestSetPaused(t *testing.T) { @@ -55,23 +47,15 @@ func TestSetPaused(t *testing.T) { result := New() result.Pause() - if !result.IsPaused() { - t.Fatalf("Expected result to be paused, got %t\n", result.IsPaused()) - } + urequire.True(t, result.IsPaused(), "Expected result to be paused") } func TestIsPaused(t *testing.T) { std.TestSetOrigCaller(firstCaller) result := New() - - if result.IsPaused() { - t.Fatalf("Expected result to be unpaused, got %t\n", result.IsPaused()) - } + urequire.False(t, result.IsPaused(), "Expected result to be unpaused") result.Pause() - - if !result.IsPaused() { - t.Fatalf("Expected result to be paused, got %t\n", result.IsPaused()) - } + urequire.True(t, result.IsPaused(), "Expected result to be paused") } diff --git a/examples/gno.land/p/demo/rand/gno.mod b/examples/gno.land/p/demo/rand/gno.mod deleted file mode 100644 index 098af152648..00000000000 --- a/examples/gno.land/p/demo/rand/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -// Draft - -module gno.land/p/demo/rand diff --git a/examples/gno.land/p/demo/rand/rand.gno b/examples/gno.land/p/demo/rand/rand.gno deleted file mode 100644 index 2fa16d627be..00000000000 --- a/examples/gno.land/p/demo/rand/rand.gno +++ /dev/null @@ -1,139 +0,0 @@ -package rand - -// Disclaimer: this package is unsafe and won't prevent others to -// guess values in advance. -// -// the goal of this package is to implement a random library that -// is fully deterministic for validators while being hard to guess. -// -// We use the Bernstein's hash djb2 to be CPU-cycle efficient. - -import ( - "math/rand" - "std" - "time" -) - -type Instance struct { - seed int64 -} - -func New() *Instance { - r := Instance{seed: 5381} - r.addEntropy() - return &r -} - -func FromSeed(seed int64) *Instance { - r := Instance{seed: seed} - r.addEntropy() - return &r -} - -func (i *Instance) Seed() int64 { - return i.seed -} - -func (i *Instance) djb2String(input string) { - for _, c := range input { - i.djb2Int64(int64(c)) - } -} - -// super fast random algorithm. -// http://www.cse.yorku.ca/~oz/hash.html -func (i *Instance) djb2Int64(input int64) { - i.seed = (i.seed << 5) + i.seed + input -} - -// AddEntropy uses various runtime variables to add entropy to the existing seed. -func (i *Instance) addEntropy() { - // FIXME: reapply the 5381 initial value? - - // inherit previous entropy - // nothing to do - - // handle callers - { - caller1 := std.GetCallerAt(1).String() - i.djb2String(caller1) - caller2 := std.GetCallerAt(2).String() - i.djb2String(caller2) - } - - // height - { - height := std.GetHeight() - i.djb2Int64(height) - } - - // time - { - secs := time.Now().Second() - i.djb2Int64(int64(secs)) - nsecs := time.Now().Nanosecond() - i.djb2Int64(int64(nsecs)) - } - - // FIXME: compute other hard-to-guess but deterministic variables, like real gas? -} - -func (i *Instance) Float32() float32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Float32() -} - -func (i *Instance) Float64() float64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Float64() -} - -func (i *Instance) Int() int { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int() -} - -func (i *Instance) Intn(n int) int { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Intn(n) -} - -func (i *Instance) Int63() int64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int63() -} - -func (i *Instance) Int63n(n int64) int64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int63n(n) -} - -func (i *Instance) Int31() int32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int31() -} - -func (i *Instance) Int31n(n int32) int32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int31n(n) -} - -func (i *Instance) Uint32() uint32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Uint32() -} - -func (i *Instance) Uint64() uint64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Uint64() -} - -func (i *Instance) Read(p []byte) (n int, err error) { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Read(p) -} - -func (i *Instance) Shuffle(n int, swap func(i, j int)) { - i.addEntropy() - rand.New(rand.NewSource(i.seed)).Shuffle(n, swap) -} diff --git a/examples/gno.land/p/demo/rand/rand0_filetest.gno b/examples/gno.land/p/demo/rand/rand0_filetest.gno deleted file mode 100644 index c5c138924fc..00000000000 --- a/examples/gno.land/p/demo/rand/rand0_filetest.gno +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "std" - - "gno.land/p/demo/rand" -) - -func main() { - // initial - println("---") - r := rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - - // should be the same - println("---") - r = rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - - std.TestSkipHeights(1) - println("---") - r = rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) -} - -// Output: -// --- -// 777 -// 257 -// 74 -// 177 -// 802 -// --- -// 777 -// 257 -// 74 -// 177 -// 802 -// --- -// 450 -// 78 -// 777 -// 15 -// 339 diff --git a/examples/gno.land/p/demo/rand/rand_test.gno b/examples/gno.land/p/demo/rand/rand_test.gno deleted file mode 100644 index 2651f0af089..00000000000 --- a/examples/gno.land/p/demo/rand/rand_test.gno +++ /dev/null @@ -1,49 +0,0 @@ -package rand - -import ( - "fmt" - "std" - "strings" - "testing" - - "gno.land/p/demo/rand" -) - -func TestInstance(t *testing.T) { - instance := rand.New() - if instance == nil { - t.Errorf("instance should not be nil") - } -} - -func TestIntn(t *testing.T) { - baseRand := rand.New() - baseResult := computeIntn(t, baseRand) - - sameHeightRand := rand.New() - sameHeightResult := computeIntn(t, sameHeightRand) - - if baseResult != sameHeightResult { - t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) - } - - std.TestSkipHeights(1) - differentHeightRand := rand.New() - differentHeightResult := computeIntn(t, differentHeightRand) - - if baseResult == differentHeightResult { - t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult) - } -} - -func computeIntn(t *testing.T, r *rand.Instance) string { - t.Helper() - - arr := []string{} - for i := 0; i < 10; i++ { - arr = append(arr, fmt.Sprintf("%d", r.Intn(1000))) - } - - out := strings.Join(arr, ",") - return out -} diff --git a/examples/gno.land/p/demo/stack/stack_test.gno b/examples/gno.land/p/demo/stack/stack_test.gno index 764c7f7f691..ee26ee3f5a4 100644 --- a/examples/gno.land/p/demo/stack/stack_test.gno +++ b/examples/gno.land/p/demo/stack/stack_test.gno @@ -1,8 +1,6 @@ package stack -import ( - "testing" -) +import "testing" func TestStack(t *testing.T) { s := New() // Empty stack diff --git a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno index 36163065e7f..4b2c04b6d5c 100644 --- a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno +++ b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno @@ -1,7 +1,6 @@ package main import ( - "std" "time" "internal/os_test" diff --git a/examples/gno.land/p/demo/tests/gno.mod b/examples/gno.land/p/demo/tests/gno.mod index 5d80e106567..d3d796f76f8 100644 --- a/examples/gno.land/p/demo/tests/gno.mod +++ b/examples/gno.land/p/demo/tests/gno.mod @@ -2,5 +2,6 @@ module gno.land/p/demo/tests require ( gno.land/p/demo/tests/subtests v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/r/demo/tests v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod new file mode 100644 index 00000000000..8585cfd9c8d --- /dev/null +++ b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/tests/p_crossrealm diff --git a/examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno b/examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno new file mode 100644 index 00000000000..6d46203e98c --- /dev/null +++ b/examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno @@ -0,0 +1,24 @@ +package p_crossrealm + +type Stringer interface { + String() string +} + +type Container struct { + A int + B Stringer +} + +func (c *Container) Touch() *Container { + c.A += 1 + return c +} + +func (c *Container) Print() { + println("A:", c.A) + if c.B == nil { + println("B: undefined") + } else { + println("B:", c.B.String()) + } +} diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno index 1a2c2526d01..43732d82dac 100644 --- a/examples/gno.land/p/demo/tests/tests.gno +++ b/examples/gno.land/p/demo/tests/tests.gno @@ -18,7 +18,7 @@ func IncCounter() { } func CurrentRealmPath() string { - return std.CurrentRealmPath() + return std.CurrentRealm().PkgPath() } //---------------------------------------- diff --git a/examples/gno.land/p/demo/tests/tests_test.gno b/examples/gno.land/p/demo/tests/tests_test.gno index 49caf2a0294..98bd0e91d08 100644 --- a/examples/gno.land/p/demo/tests/tests_test.gno +++ b/examples/gno.land/p/demo/tests/tests_test.gno @@ -4,6 +4,8 @@ import ( "testing" "gno.land/p/demo/tests" + + "gno.land/p/demo/uassert" ) var World = "WORLD" @@ -12,7 +14,6 @@ func TestGetHelloWorld(t *testing.T) { // tests.World is 'world' s := "hello " + tests.World + World const want = "hello worldWORLD" - if s != want { - t.Errorf("got %q want %q", s, want) - } + + uassert.Equal(t, want, s) } diff --git a/examples/gno.land/p/demo/todolist/gno.mod b/examples/gno.land/p/demo/todolist/gno.mod index a51528b9500..bbccf357e3b 100644 --- a/examples/gno.land/p/demo/todolist/gno.mod +++ b/examples/gno.land/p/demo/todolist/gno.mod @@ -1,3 +1,6 @@ module gno.land/p/demo/todolist -require gno.land/p/demo/avl v0.0.0-latest +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/todolist/todolist_test.gno b/examples/gno.land/p/demo/todolist/todolist_test.gno index 5b2bb361881..85836e2a17f 100644 --- a/examples/gno.land/p/demo/todolist/todolist_test.gno +++ b/examples/gno.land/p/demo/todolist/todolist_test.gno @@ -3,36 +3,25 @@ package todolist import ( "std" "testing" + + "gno.land/p/demo/uassert" ) func TestNewTodoList(t *testing.T) { title := "My Todo List" todoList := NewTodoList(title) - if todoList.GetTodolistTitle() != title { - t.Errorf("Expected title %q, got %q", title, todoList.GetTodolistTitle()) - } - - if len(todoList.GetTasks()) != 0 { - t.Errorf("Expected 0 tasks, got %d", len(todoList.GetTasks())) - } - - if todoList.GetTodolistOwner() != std.GetOrigCaller() { - t.Errorf("Expected owner %v, got %v", std.GetOrigCaller(), todoList.GetTodolistOwner()) - } + uassert.Equal(t, title, todoList.GetTodolistTitle()) + uassert.Equal(t, 0, len(todoList.GetTasks())) + uassert.Equal(t, std.GetOrigCaller().String(), todoList.GetTodolistOwner().String()) } func TestNewTask(t *testing.T) { title := "My Task" task := NewTask(title) - if task.Title != title { - t.Errorf("Expected title %q, got %q", title, task.Title) - } - - if task.Done { - t.Errorf("Expected task to be not done, but it is done") - } + uassert.Equal(t, title, task.Title) + uassert.False(t, task.Done, "Expected task to be not done, but it is done") } func TestAddTask(t *testing.T) { @@ -42,29 +31,19 @@ func TestAddTask(t *testing.T) { todoList.AddTask(1, task) tasks := todoList.GetTasks() - if len(tasks) != 1 { - t.Errorf("Expected 1 task, got %d", len(tasks)) - } - if tasks[0] != task { - t.Errorf("Expected task %v, got %v", task, tasks[0]) - } + uassert.Equal(t, 1, len(tasks)) + uassert.True(t, tasks[0] == task, "Task does not match") } func TestToggleTaskStatus(t *testing.T) { task := NewTask("My Task") ToggleTaskStatus(task) - - if !task.Done { - t.Errorf("Expected task to be done, but it is not done") - } + uassert.True(t, task.Done, "Expected task to be done, but it is not done") ToggleTaskStatus(task) - - if task.Done { - t.Errorf("Expected task to be not done, but it is done") - } + uassert.False(t, task.Done, "Expected task to be done, but it is not done") } func TestRemoveTask(t *testing.T) { @@ -75,7 +54,5 @@ func TestRemoveTask(t *testing.T) { todoList.RemoveTask("1") tasks := todoList.GetTasks() - if len(tasks) != 0 { - t.Errorf("Expected 0 tasks, got %d", len(tasks)) - } + uassert.Equal(t, 0, len(tasks)) } diff --git a/examples/gno.land/p/demo/uassert/doc.gno b/examples/gno.land/p/demo/uassert/doc.gno new file mode 100644 index 00000000000..df95d78e394 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/doc.gno @@ -0,0 +1 @@ +package uassert // import "gno.land/p/demo/uassert" diff --git a/examples/gno.land/p/demo/uassert/gno.mod b/examples/gno.land/p/demo/uassert/gno.mod new file mode 100644 index 00000000000..f22276564bf --- /dev/null +++ b/examples/gno.land/p/demo/uassert/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/uassert + +require gno.land/p/demo/diff v0.0.0-latest diff --git a/examples/gno.land/p/demo/uassert/helpers.gno b/examples/gno.land/p/demo/uassert/helpers.gno new file mode 100644 index 00000000000..76657e75ed4 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/helpers.gno @@ -0,0 +1,51 @@ +package uassert + +import "strings" + +func fail(t TestingT, customMsgs []string, failureMessage string, args ...interface{}) bool { + customMsg := "" + if len(customMsgs) > 0 { + customMsg = strings.Join(customMsgs, " ") + } + if customMsg != "" { + failureMessage += " - " + customMsg + } + t.Errorf(failureMessage, args...) + return false +} + +func autofail(t TestingT, success bool, customMsgs []string, failureMessage string, args ...interface{}) bool { + if success { + return true + } + return fail(t, customMsgs, failureMessage, args...) +} + +func checkDidPanic(f func()) (didPanic bool, message string) { + didPanic = true + defer func() { + r := recover() + + if r == nil { + message = "nil" + return + } + + err, ok := r.(error) + if ok { + message = err.Error() + return + } + + errStr, ok := r.(string) + if ok { + message = errStr + return + } + + message = "recover: unsupported type" + }() + f() + didPanic = false + return +} diff --git a/examples/gno.land/p/demo/uassert/mock_test.gno b/examples/gno.land/p/demo/uassert/mock_test.gno new file mode 100644 index 00000000000..b611faab668 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/mock_test.gno @@ -0,0 +1,59 @@ +package uassert + +import ( + "fmt" + "testing" +) + +type mockTestingT struct { + fmt string + args []interface{} +} + +// --- 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) Fatalf(fmt string, args ...interface{}) { + mockT.fmt = "fatal: " + fmt + mockT.args = args +} + +func (mockT *mockTestingT) Errorf(fmt string, args ...interface{}) { + mockT.fmt = "error: " + fmt + mockT.args = args +} + +// --- helpers + +func (mockT *mockTestingT) actualString() string { + res := fmt.Sprintf(mockT.fmt, mockT.args...) + mockT.reset() + return res +} + +func (mockT *mockTestingT) reset() { + mockT.fmt = "" + mockT.args = nil +} + +func (mockT *mockTestingT) equals(t *testing.T, expected string) { + actual := mockT.actualString() + + if expected != actual { + t.Errorf("mockT differs:\n- expected: %s\n- actual: %s\n", expected, actual) + } +} + +func (mockT *mockTestingT) empty(t *testing.T) { + if mockT.fmt != "" || mockT.args != nil { + actual := mockT.actualString() + t.Errorf("mockT should be empty, got %s", actual) + } +} diff --git a/examples/gno.land/p/demo/uassert/types.gno b/examples/gno.land/p/demo/uassert/types.gno new file mode 100644 index 00000000000..83950b5e8a4 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/types.gno @@ -0,0 +1,11 @@ +package uassert + +type TestingT interface { + Helper() + Skip(args ...interface{}) + Fatalf(fmt string, args ...interface{}) + Errorf(fmt string, args ...interface{}) + Logf(fmt string, args ...interface{}) + Fail() + FailNow() +} diff --git a/examples/gno.land/p/demo/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno new file mode 100644 index 00000000000..7b3254ea505 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -0,0 +1,424 @@ +// uassert is an adapted lighter version of https://github.com/stretchr/testify/assert. +package uassert + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/diff" +) + +// NoError asserts that a function returned no error (i.e. `nil`). +func NoError(t TestingT, err error, msgs ...string) bool { + t.Helper() + if err != nil { + return fail(t, msgs, "unexpected error: %s", err.Error()) + } + return true +} + +// Error asserts that a function returned an error (i.e. not `nil`). +func Error(t TestingT, err error, msgs ...string) bool { + t.Helper() + if err == nil { + return fail(t, msgs, "an error is expected but got nil") + } + return true +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +func ErrorContains(t TestingT, err error, contains string, msgs ...string) bool { + t.Helper() + + if !Error(t, err, msgs...) { + return false + } + + actual := err.Error() + if !strings.Contains(actual, contains) { + return fail(t, msgs, "error %q does not contain %q", actual, contains) + } + + return true +} + +// True asserts that the specified value is true. +func True(t TestingT, value bool, msgs ...string) bool { + t.Helper() + if !value { + return fail(t, msgs, "should be true") + } + return true +} + +// False asserts that the specified value is false. +func False(t TestingT, value bool, msgs ...string) bool { + t.Helper() + if value { + return fail(t, msgs, "should be false") + } + return true +} + +// ErrorIs asserts the given error matches the target error +func ErrorIs(t TestingT, err, target error, msgs ...string) bool { + t.Helper() + + if err == nil || target == nil { + return err == target + } + + // XXX: if errors.Is(err, target) return true + + if err.Error() != target.Error() { + return fail(t, msgs, "error mismatch, expected %s, got %s", target.Error(), err.Error()) + } + + return true +} + +// PanicsWithMessage asserts that the code inside the specified func panics, +// and that the recovered panic value satisfies the given message +func PanicsWithMessage(t TestingT, msg string, f func(), msgs ...string) bool { + t.Helper() + + didPanic, panicValue := checkDidPanic(f) + if !didPanic { + return fail(t, msgs, "func should panic\n\tPanic value:\t%v", panicValue) + } + + if panicValue != msg { + return fail(t, msgs, "func should panic with message:\t%s\n\tPanic value:\t%s", msg, panicValue) + } + return true +} + +// NotPanics asserts that the code inside the specified func does NOT panic. +func NotPanics(t TestingT, f func(), msgs ...string) bool { + t.Helper() + + didPanic, panicValue := checkDidPanic(f) + + if didPanic { + return fail(t, msgs, "func should not panic\n\tPanic value:\t%s", panicValue) + } + return true +} + +// Equal asserts that two objects are equal. +func Equal(t TestingT, expected, actual interface{}, msgs ...string) bool { + t.Helper() + + if expected == nil || actual == nil { + return expected == actual + } + + // XXX: errors + // XXX: slices + // XXX: pointers + + equal := false + ok_ := false + es, as := "unsupported type", "unsupported type" + + switch ev := expected.(type) { + case string: + if av, ok := actual.(string); ok { + equal = ev == av + ok_ = true + es, as = ev, av + if !equal { + dif := diff.MyersDiff(ev, av) + return fail(t, msgs, "uassert.Equal: strings are different\n\tDiff: %s", diff.Format(dif)) + } + } + case std.Address: + if av, ok := actual.(std.Address); ok { + equal = ev == av + ok_ = true + es, as = string(ev), string(av) + } + case int: + if av, ok := actual.(int); ok { + equal = ev == av + ok_ = true + es, as = strconv.Itoa(ev), strconv.Itoa(av) + } + case int8: + if av, ok := actual.(int8); ok { + equal = ev == av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case int16: + if av, ok := actual.(int16); ok { + equal = ev == av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case int32: + if av, ok := actual.(int32); ok { + equal = ev == av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case int64: + if av, ok := actual.(int64); ok { + equal = ev == av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case uint: + if av, ok := actual.(uint); ok { + equal = ev == av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint8: + if av, ok := actual.(uint8); ok { + equal = ev == av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint16: + if av, ok := actual.(uint16); ok { + equal = ev == av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint32: + if av, ok := actual.(uint32); ok { + equal = ev == av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint64: + if av, ok := actual.(uint64); ok { + equal = ev == av + ok_ = true + es, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10) + } + case bool: + if av, ok := actual.(bool); ok { + equal = ev == av + ok_ = true + if ev { + es, as = "true", "false" + } else { + es, as = "false", "true" + } + } + case float32: + if av, ok := actual.(float32); ok { + equal = ev == av + ok_ = true + } + case float64: + if av, ok := actual.(float64); ok { + equal = ev == av + ok_ = true + } + default: + return fail(t, msgs, "uassert.Equal: unsupported type") + } + + /* + // XXX: implement stringer and other well known similar interfaces + type stringer interface{ String() string } + if ev, ok := expected.(stringer); ok { + if av, ok := actual.(stringer); ok { + equal = ev.String() == av.String() + ok_ = true + } + } + */ + + if !ok_ { + return fail(t, msgs, "uassert.Equal: different types") // XXX: display the types + } + if !equal { + return fail(t, msgs, "uassert.Equal: same type but different value\n\texpected: %s\n\tactual: %s", es, as) + } + + return true +} + +// NotEqual asserts that two objects are not equal. +func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool { + t.Helper() + + if expected == nil || actual == nil { + return expected != actual + } + + // XXX: errors + // XXX: slices + // XXX: pointers + + notEqual := false + ok_ := false + es, as := "unsupported type", "unsupported type" + + switch ev := expected.(type) { + case string: + if av, ok := actual.(string); ok { + notEqual = ev != av + ok_ = true + es, as = ev, as + } + case std.Address: + if av, ok := actual.(std.Address); ok { + notEqual = ev != av + ok_ = true + es, as = string(ev), string(av) + } + case int: + if av, ok := actual.(int); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.Itoa(ev), strconv.Itoa(av) + } + case int8: + if av, ok := actual.(int8); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case int16: + if av, ok := actual.(int16); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case int32: + if av, ok := actual.(int32); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case int64: + if av, ok := actual.(int64); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av)) + } + case uint: + if av, ok := actual.(uint); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint8: + if av, ok := actual.(uint8); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint16: + if av, ok := actual.(uint16); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint32: + if av, ok := actual.(uint32); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10) + } + case uint64: + if av, ok := actual.(uint64); ok { + notEqual = ev != av + ok_ = true + es, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10) + } + case bool: + if av, ok := actual.(bool); ok { + notEqual = ev != av + ok_ = true + if ev { + es, as = "true", "false" + } else { + es, as = "false", "true" + } + } + case float32: + if av, ok := actual.(float32); ok { + notEqual = ev != av + ok_ = true + } + case float64: + if av, ok := actual.(float64); ok { + notEqual = ev != av + ok_ = true + } + default: + return fail(t, msgs, "uassert.NotEqual: unsupported type") + } + + /* + // XXX: implement stringer and other well known similar interfaces + type stringer interface{ String() string } + if ev, ok := expected.(stringer); ok { + if av, ok := actual.(stringer); ok { + notEqual = ev.String() != av.String() + ok_ = true + } + } + */ + + if !ok_ { + return fail(t, msgs, "uassert.NotEqual: different types") // XXX: display the types + } + if !notEqual { + return fail(t, msgs, "uassert.NotEqual: same type and same value\n\texpected: %s\n\tactual: %s", es, as) + } + + return true +} + +func Empty(t TestingT, obj interface{}, msgs ...string) bool { + t.Helper() + switch val := obj.(type) { + case string: + if val != "" { + return fail(t, msgs, "uassert.Empty: not empty string: %s", val) + } + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + if val != 0 { + return fail(t, msgs, "uassert.Empty: not empty number: %d", val) + } + case std.Address: + var zeroAddr std.Address + if val != zeroAddr { + return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val)) + } + default: + return fail(t, msgs, "uassert.Empty: unsupported type") + } + return true +} + +func NotEmpty(t TestingT, obj interface{}, msgs ...string) bool { + t.Helper() + switch val := obj.(type) { + case string: + if val == "" { + return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val) + } + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + if val == 0 { + return fail(t, msgs, "uassert.NotEmpty: empty number: %d", val) + } + case std.Address: + var zeroAddr std.Address + if val == zeroAddr { + return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val)) + } + default: + return fail(t, msgs, "uassert.NotEmpty: unsupported type") + } + return true +} diff --git a/examples/gno.land/p/demo/uassert/uassert_test.gno b/examples/gno.land/p/demo/uassert/uassert_test.gno new file mode 100644 index 00000000000..5ead848fd15 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/uassert_test.gno @@ -0,0 +1,365 @@ +package uassert + +import ( + "errors" + "fmt" + "std" + "testing" +) + +var _ TestingT = (*testing.T)(nil) + +func TestMock(t *testing.T) { + mockT := new(mockTestingT) + mockT.empty(t) + NoError(mockT, errors.New("foo")) + mockT.equals(t, "error: unexpected error: foo") + NoError(mockT, errors.New("foo"), "custom message") + mockT.equals(t, "error: unexpected error: foo - custom message") + NoError(mockT, errors.New("foo"), "custom", "message") + mockT.equals(t, "error: unexpected error: foo - custom message") +} + +func TestNoError(t *testing.T) { + mockT := new(mockTestingT) + True(t, NoError(mockT, nil)) + mockT.empty(t) + False(t, NoError(mockT, errors.New("foo bar"))) + mockT.equals(t, "error: unexpected error: foo bar") +} + +func TestError(t *testing.T) { + mockT := new(mockTestingT) + True(t, Error(mockT, errors.New("foo bar"))) + mockT.empty(t) + False(t, Error(mockT, nil)) + mockT.equals(t, "error: an error is expected but got nil") +} + +func TestErrorContains(t *testing.T) { + mockT := new(mockTestingT) + + // nil error + var err error + False(t, ErrorContains(mockT, err, ""), "ErrorContains should return false for nil arg") +} + +func TestTrue(t *testing.T) { + mockT := new(mockTestingT) + if !True(mockT, true) { + t.Error("True should return true") + } + mockT.empty(t) + if True(mockT, false) { + t.Error("True should return false") + } + mockT.equals(t, "error: should be true") +} + +func TestFalse(t *testing.T) { + mockT := new(mockTestingT) + if !False(mockT, false) { + t.Error("False should return true") + } + mockT.empty(t) + if False(mockT, true) { + t.Error("False should return false") + } + mockT.equals(t, "error: should be false") +} + +func TestPanicsWithMessage(t *testing.T) { + mockT := new(mockTestingT) + if !PanicsWithMessage(mockT, "panic", func() { + panic(errors.New("panic")) + }) { + t.Error("PanicsWithMessage should return true") + } + mockT.empty(t) + + if PanicsWithMessage(mockT, "Panic!", func() { + // noop + }) { + t.Error("PanicsWithMessage should return false") + } + mockT.equals(t, "error: func should panic\n\tPanic value:\tnil") + + if PanicsWithMessage(mockT, "at the disco", func() { + panic(errors.New("panic")) + }) { + t.Error("PanicsWithMessage should return false") + } + mockT.equals(t, "error: func should panic with message:\tat the disco\n\tPanic value:\tpanic") + + if PanicsWithMessage(mockT, "Panic!", func() { + panic("panic") + }) { + t.Error("PanicsWithMessage should return false") + } + mockT.equals(t, "error: func should panic with message:\tPanic!\n\tPanic value:\tpanic") +} + +func TestNotPanics(t *testing.T) { + mockT := new(mockTestingT) + + if !NotPanics(mockT, func() { + // noop + }) { + t.Error("NotPanics should return true") + } + mockT.empty(t) + + if NotPanics(mockT, func() { + panic("Panic!") + }) { + t.Error("NotPanics should return false") + } +} + +func TestEqual(t *testing.T) { + mockT := new(mockTestingT) + + cases := []struct { + expected interface{} + actual interface{} + result bool + remark string + }{ + // expected to be equal + {"Hello World", "Hello World", true, ""}, + {123, 123, true, ""}, + {123.5, 123.5, true, ""}, + {nil, nil, true, ""}, + {int32(123), int32(123), true, ""}, + {uint64(123), uint64(123), true, ""}, + {std.Address("g12345"), std.Address("g12345"), true, ""}, + // XXX: continue + + // not expected to be equal + {"Hello World", 42, false, ""}, + {41, 42, false, ""}, + {10, uint(10), false, ""}, + // XXX: continue + + // expected to raise errors + // XXX: todo + } + + for _, c := range cases { + name := fmt.Sprintf("Equal(%v, %v)", c.expected, c.actual) + t.Run(name, func(t *testing.T) { + res := Equal(mockT, c.expected, c.actual) + + if res != c.result { + t.Errorf("%s should return %v: %s - %s", name, c.result, c.remark, mockT.actualString()) + } + }) + } +} + +func TestNotEqual(t *testing.T) { + mockT := new(mockTestingT) + + cases := []struct { + expected interface{} + actual interface{} + result bool + remark string + }{ + // expected to be not equal + {"Hello World", "Hello", true, ""}, + {123, 124, true, ""}, + {123.5, 123.6, true, ""}, + {nil, 123, true, ""}, + {int32(123), int32(124), true, ""}, + {uint64(123), uint64(124), true, ""}, + {std.Address("g12345"), std.Address("g67890"), true, ""}, + // XXX: continue + + // not expected to be not equal + {"Hello World", "Hello World", false, ""}, + {123, 123, false, ""}, + {123.5, 123.5, false, ""}, + {nil, nil, false, ""}, + {int32(123), int32(123), false, ""}, + {uint64(123), uint64(123), false, ""}, + {std.Address("g12345"), std.Address("g12345"), false, ""}, + // XXX: continue + + // expected to raise errors + // XXX: todo + } + + for _, c := range cases { + name := fmt.Sprintf("NotEqual(%v, %v)", c.expected, c.actual) + t.Run(name, func(t *testing.T) { + res := NotEqual(mockT, c.expected, c.actual) + + if res != c.result { + t.Errorf("%s should return %v: %s - %s", name, c.result, c.remark, mockT.actualString()) + } + }) + } +} + +type myStruct struct { + S string + I int +} + +func TestEmpty(t *testing.T) { + mockT := new(mockTestingT) + + cases := []struct { + obj interface{} + expectedEmpty bool + }{ + // expected to be empty + {"", true}, + {0, true}, + {int(0), true}, + {int64(0), true}, + {uint(0), true}, + // XXX: continue + + // not expected to be empty + {"Hello World", false}, + {1, false}, + {int32(1), false}, + {uint64(1), false}, + {std.Address("g12345"), false}, + + // unsupported + {nil, false}, + {myStruct{}, false}, + {&myStruct{}, false}, + } + + for _, c := range cases { + name := fmt.Sprintf("Empty(%v)", c.obj) + t.Run(name, func(t *testing.T) { + res := Empty(mockT, c.obj) + + if res != c.expectedEmpty { + t.Errorf("%s should return %v: %s", name, c.expectedEmpty, mockT.actualString()) + } + }) + } +} + +func TestEqualWithStringDiff(t *testing.T) { + cases := []struct { + name string + expected string + actual string + shouldPass bool + expectedMsg string + }{ + { + name: "Identical strings", + expected: "Hello, world!", + actual: "Hello, world!", + shouldPass: true, + expectedMsg: "", + }, + { + name: "Different strings - simple", + expected: "Hello, world!", + actual: "Hello, World!", + shouldPass: false, + expectedMsg: "error: uassert.Equal: strings are different\n\tDiff: Hello, [-w][+W]orld!", + }, + { + name: "Different strings - complex", + expected: "The quick brown fox jumps over the lazy dog", + actual: "The quick brown cat jumps over the lazy dog", + shouldPass: false, + expectedMsg: "error: uassert.Equal: strings are different\n\tDiff: The quick brown [-fox][+cat] jumps over the lazy dog", + }, + { + name: "Different strings - prefix", + expected: "prefix_string", + actual: "string", + shouldPass: false, + expectedMsg: "error: uassert.Equal: strings are different\n\tDiff: [-prefix_]string", + }, + { + name: "Different strings - suffix", + expected: "string", + actual: "string_suffix", + shouldPass: false, + expectedMsg: "error: uassert.Equal: strings are different\n\tDiff: string[+_suffix]", + }, + { + name: "Empty string vs non-empty string", + expected: "", + actual: "non-empty", + shouldPass: false, + expectedMsg: "error: uassert.Equal: strings are different\n\tDiff: [+non-empty]", + }, + { + name: "Non-empty string vs empty string", + expected: "non-empty", + actual: "", + shouldPass: false, + expectedMsg: "error: uassert.Equal: strings are different\n\tDiff: [-non-empty]", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mockT := &mockTestingT{} + result := Equal(mockT, tc.expected, tc.actual) + + if result != tc.shouldPass { + t.Errorf("Expected Equal to return %v, but got %v", tc.shouldPass, result) + } + + if tc.shouldPass { + mockT.empty(t) + } else { + mockT.equals(t, tc.expectedMsg) + } + }) + } +} + +func TestNotEmpty(t *testing.T) { + mockT := new(mockTestingT) + + cases := []struct { + obj interface{} + expectedNotEmpty bool + }{ + // expected to be empty + {"", false}, + {0, false}, + {int(0), false}, + {int64(0), false}, + {uint(0), false}, + {std.Address(""), false}, + + // not expected to be empty + {"Hello World", true}, + {1, true}, + {int32(1), true}, + {uint64(1), true}, + {std.Address("g12345"), true}, + + // unsupported + {nil, false}, + {myStruct{}, false}, + {&myStruct{}, false}, + } + + for _, c := range cases { + name := fmt.Sprintf("NotEmpty(%v)", c.obj) + t.Run(name, func(t *testing.T) { + res := NotEmpty(mockT, c.obj) + + if res != c.expectedNotEmpty { + t.Errorf("%s should return %v: %s", name, c.expectedNotEmpty, mockT.actualString()) + } + }) + } +} diff --git a/examples/gno.land/p/demo/ufmt/ufmt.gno b/examples/gno.land/p/demo/ufmt/ufmt.gno index a7cd8550fff..55494e32cec 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt.gno @@ -4,6 +4,7 @@ package ufmt import ( + "errors" "strconv" "strings" ) @@ -17,16 +18,20 @@ func Println(args ...interface{}) { switch v := arg.(type) { case string: strs = append(strs, v) + case (interface{ String() string }): + strs = append(strs, v.String()) + case error: + strs = append(strs, v.Error()) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: strs = append(strs, Sprintf("%d", v)) case bool: if v { strs = append(strs, "true") - - continue + } else { + strs = append(strs, "false") } - - strs = append(strs, "false") + case nil: + strs = append(strs, "") default: strs = append(strs, "(unhandled)") } @@ -46,7 +51,9 @@ func Println(args ...interface{}) { // // %s: places a string value directly. // If the value implements the interface interface{ String() string }, -// the String() method is called to retrieve the value. +// the String() method is called to retrieve the value. Same about Error() +// string. +// %c: formats the character represented by Unicode code point // %d: formats an integer value using package "strconv". // Currently supports only uint, uint64, int, int64. // %t: formats a boolean value to "true" or "false". @@ -88,10 +95,32 @@ func Sprintf(format string, args ...interface{}) string { switch v := arg.(type) { case interface{ String() string }: buf += v.String() + case error: + buf += v.Error() case string: buf += v default: - buf += "(unhandled)" + buf += fallback(verb, v) + } + case "c": + switch v := arg.(type) { + // rune is int32. Exclude overflowing numeric types and dups (byte, int32): + case rune: + buf += string(v) + case int: + buf += string(v) + case int8: + buf += string(v) + case int16: + buf += string(v) + case uint: + buf += string(v) + case uint8: + buf += string(v) + case uint16: + buf += string(v) + default: + buf += fallback(verb, v) } case "d": switch v := arg.(type) { @@ -116,7 +145,7 @@ func Sprintf(format string, args ...interface{}) string { case uint64: buf += strconv.FormatUint(v, 10) default: - buf += "(unhandled)" + buf += fallback(verb, v) } case "t": switch v := arg.(type) { @@ -127,11 +156,11 @@ func Sprintf(format string, args ...interface{}) string { buf += "false" } default: - buf += "(unhandled)" + buf += fallback(verb, v) } // % handled before, as it does not consume an argument default: - buf += "(unhandled)" + buf += "(unhandled verb: %" + verb + ")" } i += 2 @@ -142,6 +171,85 @@ func Sprintf(format string, args ...interface{}) string { return buf } +// This function is used to mimic Go's fmt.Sprintf +// specific behaviour of showing verb/type mismatches, +// where for example: +// +// fmt.Sprintf("%d", "foo") gives "%!d(string=foo)" +// +// Here: +// +// fallback("s", 8) -> "%!s(int=8)" +// fallback("d", nil) -> "%!d()", and so on. +func fallback(verb string, arg interface{}) string { + var s string + switch v := arg.(type) { + case string: + s = "string=" + v + case (interface{ String() string }): + s = "string=" + v.String() + case error: + // note: also "string=" in Go fmt + s = "string=" + v.Error() + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + // note: rune, byte would be dups, being aliases + if typename, e := typeToString(v); e != nil { + panic("should not happen") + } else { + s = typename + "=" + Sprintf("%d", v) + } + case bool: + if v { + s = "bool=true" + } else { + s = "bool=false" + } + case nil: + s = "" + default: + s = "(unhandled)" + } + return "%!" + verb + "(" + s + ")" +} + +// 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) { + switch v.(type) { + case string: + return "string", nil + case int: + return "int", nil + case int8: + return "int8", nil + case int16: + return "int16", nil + case int32: + return "int32", nil + case int64: + return "int64", nil + case uint: + return "uint", nil + case uint8: + return "uint8", nil + case uint16: + return "uint16", nil + case uint32: + return "uint32", nil + case uint64: + return "uint64", nil + case float32: + return "float32", nil + case float64: + return "float64", nil + case bool: + return "bool", nil + default: + return "", errors.New("(unsupported type)") + } +} + // errMsg implements the error interface. type errMsg struct { msg string @@ -165,7 +273,8 @@ func (e *errMsg) Error() string { // // %s: places a string value directly. // If the value implements the interface interface{ String() string }, -// the String() method is called to retrieve the value. +// the String() method is called to retrieve the value. Same for error. +// %c: formats the character represented by Unicode code point // %d: formats an integer value using package "strconv". // Currently supports only uint, uint64, int, int64. // %t: formats a boolean value to "true" or "false". diff --git a/examples/gno.land/p/demo/ufmt/ufmt_test.gno b/examples/gno.land/p/demo/ufmt/ufmt_test.gno index 94d32372d30..d53fb39bc44 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt_test.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt_test.gno @@ -1,6 +1,7 @@ package ufmt import ( + "errors" "fmt" "testing" ) @@ -12,6 +13,7 @@ func (stringer) String() string { } func TestSprintf(t *testing.T) { + tru := true cases := []struct { format string values []interface{} @@ -19,6 +21,7 @@ func TestSprintf(t *testing.T) { }{ {"hello %s!", []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]"}, {"int8 [%d]", []interface{}{int8(8)}, "int8 [8]"}, @@ -32,15 +35,36 @@ func TestSprintf(t *testing.T) { {"uint64 [%d]", []interface{}{uint64(64)}, "uint64 [64]"}, {"bool [%t]", []interface{}{true}, "bool [true]"}, {"bool [%t]", []interface{}{false}, "bool [false]"}, - {"invalid bool [%t]", []interface{}{"invalid"}, "invalid bool [(unhandled)]"}, - {"invalid integer [%d]", []interface{}{"invalid"}, "invalid integer [(unhandled)]"}, - {"invalid string [%s]", []interface{}{1}, "invalid string [(unhandled)]"}, {"no args", nil, "no args"}, {"finish with %", nil, "finish with %"}, {"stringer [%s]", []interface{}{stringer{}}, "stringer [I'm a stringer]"}, {"â", nil, "â"}, {"Hello, World! 😊", nil, "Hello, World! 😊"}, {"unicode formatting: %s", []interface{}{"😊"}, "unicode formatting: 😊"}, + // 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)"}, + + {"%c", []interface{}{nil}, "%!c()"}, + {"%c", []interface{}{421}, "ƥ"}, + {"%c", []interface{}{"z"}, "%!c(string=z)"}, + {"%c", []interface{}{tru}, "%!c(bool=true)"}, + {"%c", []interface{}{'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"}, + + {"%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)"}, } for _, tc := range cases { @@ -103,6 +127,14 @@ func TestErrorf(t *testing.T) { } } +func TestPrintErrors(t *testing.T) { + got := Sprintf("error: %s", errors.New("can I be printed?")) + expectedOutput := "error: can I be printed?" + if got != expectedOutput { + t.Errorf("got %q, want %q.", got, expectedOutput) + } +} + // NOTE: Currently, there is no way to get the output of Println without using os.Stdout, // so we can only test that it doesn't panic and print arguments well. func TestPrintln(t *testing.T) { diff --git a/examples/gno.land/p/demo/ui/ui.gno b/examples/gno.land/p/demo/ui/ui.gno index dd21d0510eb..f283effe580 100644 --- a/examples/gno.land/p/demo/ui/ui.gno +++ b/examples/gno.land/p/demo/ui/ui.gno @@ -10,6 +10,7 @@ type DOM struct { Prefix string Title string WithComments bool + Classes []string // elements Header Element @@ -18,8 +19,14 @@ type DOM struct { } func (dom DOM) String() string { + classes := strings.Join(dom.Classes, " ") + output := "" + if classes != "" { + output += "
" + "\n\n" + } + if dom.Title != "" { output += H1(dom.Title).String(dom) + "\n" } @@ -54,6 +61,10 @@ func (dom DOM) String() string { } } + if classes != "" { + output += "
" + } + // TODO: cleanup double new-lines. return output diff --git a/examples/gno.land/p/demo/uint256/bitwise_test.gno b/examples/gno.land/p/demo/uint256/bitwise_test.gno index 4437e7d5c50..aba89edfabf 100644 --- a/examples/gno.land/p/demo/uint256/bitwise_test.gno +++ b/examples/gno.land/p/demo/uint256/bitwise_test.gno @@ -1,8 +1,6 @@ package uint256 -import ( - "testing" -) +import "testing" type logicOpTest struct { name string diff --git a/examples/gno.land/p/demo/uint256/conversion_test.gno b/examples/gno.land/p/demo/uint256/conversion_test.gno index 12ae99cc0e9..ee3aad0f819 100644 --- a/examples/gno.land/p/demo/uint256/conversion_test.gno +++ b/examples/gno.land/p/demo/uint256/conversion_test.gno @@ -1,8 +1,6 @@ package uint256 -import ( - "testing" -) +import "testing" func TestIsUint64(t *testing.T) { tests := []struct { diff --git a/examples/gno.land/p/demo/urequire/gno.mod b/examples/gno.land/p/demo/urequire/gno.mod new file mode 100644 index 00000000000..9689a2222ac --- /dev/null +++ b/examples/gno.land/p/demo/urequire/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/urequire + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/urequire/urequire.gno b/examples/gno.land/p/demo/urequire/urequire.gno new file mode 100644 index 00000000000..94721dd85a7 --- /dev/null +++ b/examples/gno.land/p/demo/urequire/urequire.gno @@ -0,0 +1,103 @@ +// urequire is a sister package for uassert. +// XXX: codegen the package. +package urequire + +import "gno.land/p/demo/uassert" + +// type TestingT = uassert.TestingT // XXX: bug, should work + +func NoError(t uassert.TestingT, err error, msgs ...string) { + t.Helper() + if uassert.NoError(t, err, msgs...) { + return + } + t.FailNow() +} + +func Error(t uassert.TestingT, err error, msgs ...string) { + t.Helper() + if uassert.Error(t, err, msgs...) { + return + } + t.FailNow() +} + +func ErrorContains(t uassert.TestingT, err error, contains string, msgs ...string) { + t.Helper() + if uassert.ErrorContains(t, err, contains, msgs...) { + return + } + t.FailNow() +} + +func True(t uassert.TestingT, value bool, msgs ...string) { + t.Helper() + if uassert.True(t, value, msgs...) { + return + } + t.FailNow() +} + +func False(t uassert.TestingT, value bool, msgs ...string) { + t.Helper() + if uassert.False(t, value, msgs...) { + return + } + t.FailNow() +} + +func ErrorIs(t uassert.TestingT, err, target error, msgs ...string) { + t.Helper() + if uassert.ErrorIs(t, err, target, msgs...) { + return + } + t.FailNow() +} + +func PanicsWithMessage(t uassert.TestingT, msg string, f func(), msgs ...string) { + t.Helper() + if uassert.PanicsWithMessage(t, msg, f, msgs...) { + return + } + t.FailNow() +} + +func NotPanics(t uassert.TestingT, f func(), msgs ...string) { + t.Helper() + if uassert.NotPanics(t, f, msgs...) { + return + } + t.FailNow() +} + +func Equal(t uassert.TestingT, expected, actual interface{}, msgs ...string) { + t.Helper() + if uassert.Equal(t, expected, actual, msgs...) { + return + } + t.FailNow() +} + +func NotEqual(t uassert.TestingT, expected, actual interface{}, msgs ...string) { + t.Helper() + if uassert.NotEqual(t, expected, actual, msgs...) { + return + } + t.FailNow() +} + +func Empty(t uassert.TestingT, obj interface{}, msgs ...string) { + t.Helper() + if uassert.Empty(t, obj, msgs...) { + return + } + t.FailNow() +} + +func NotEmpty(t uassert.TestingT, obj interface{}, msgs ...string) { + t.Helper() + if uassert.NotEmpty(t, obj, msgs...) { + return + } + t.FailNow() +} diff --git a/examples/gno.land/p/demo/urequire/urequire_test.gno b/examples/gno.land/p/demo/urequire/urequire_test.gno new file mode 100644 index 00000000000..463e3fffc07 --- /dev/null +++ b/examples/gno.land/p/demo/urequire/urequire_test.gno @@ -0,0 +1,8 @@ +package urequire + +import "testing" + +func TestPackage(t *testing.T) { + Equal(t, 42, 42) + // XXX: find a way to unit test this package +} diff --git a/examples/gno.land/p/demo/watchdog/gno.mod b/examples/gno.land/p/demo/watchdog/gno.mod new file mode 100644 index 00000000000..29005441401 --- /dev/null +++ b/examples/gno.land/p/demo/watchdog/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/watchdog + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/watchdog/watchdog.gno b/examples/gno.land/p/demo/watchdog/watchdog.gno new file mode 100644 index 00000000000..6b4591b4938 --- /dev/null +++ b/examples/gno.land/p/demo/watchdog/watchdog.gno @@ -0,0 +1,39 @@ +package watchdog + +import "time" + +type Watchdog struct { + Duration time.Duration + lastUpdate time.Time + lastDown time.Time +} + +func (w *Watchdog) Alive() { + now := time.Now() + if !w.IsAlive() { + w.lastDown = now + } + w.lastUpdate = now +} + +func (w Watchdog) Status() string { + if w.IsAlive() { + return "OK" + } + return "KO" +} + +func (w Watchdog) IsAlive() bool { + return time.Since(w.lastUpdate) < w.Duration +} + +func (w Watchdog) UpSince() time.Time { + return w.lastDown +} + +func (w Watchdog) DownSince() time.Time { + if !w.IsAlive() { + return w.lastUpdate + } + return time.Time{} +} diff --git a/examples/gno.land/p/demo/watchdog/watchdog_test.gno b/examples/gno.land/p/demo/watchdog/watchdog_test.gno new file mode 100644 index 00000000000..eb7121fc4c3 --- /dev/null +++ b/examples/gno.land/p/demo/watchdog/watchdog_test.gno @@ -0,0 +1,16 @@ +package watchdog + +import ( + "testing" + "time" + + "gno.land/p/demo/uassert" +) + +func TestPackage(t *testing.T) { + w := Watchdog{Duration: 5 * time.Minute} + uassert.False(t, w.IsAlive()) + w.Alive() + uassert.True(t, w.IsAlive()) + // XXX: add more tests when we'll be able to "skip time". +} diff --git a/examples/gno.land/p/gov/proposal/gno.mod b/examples/gno.land/p/gov/proposal/gno.mod new file mode 100644 index 00000000000..3f6ef34a759 --- /dev/null +++ b/examples/gno.land/p/gov/proposal/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/gov/proposal + +require ( + gno.land/p/demo/context v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/gov/proposal/proposal.gno b/examples/gno.land/p/gov/proposal/proposal.gno new file mode 100644 index 00000000000..ca1767228c9 --- /dev/null +++ b/examples/gno.land/p/gov/proposal/proposal.gno @@ -0,0 +1,106 @@ +// Package proposal provides a structure for executing proposals. +package proposal + +import ( + "errors" + "std" + + "gno.land/p/demo/context" +) + +var errNotGovDAO = errors.New("only r/gov/dao can be the caller") + +// NewExecutor creates a new executor with the provided callback function. +func NewExecutor(callback func() error) Executor { + return &executorImpl{ + callback: callback, + done: false, + } +} + +// NewCtxExecutor creates a new executor with the provided callback function. +func NewCtxExecutor(callback func(ctx context.Context) error) Executor { + return &executorImpl{ + callbackCtx: callback, + done: false, + } +} + +// executorImpl is an implementation of the Executor interface. +type executorImpl struct { + callback func() error + callbackCtx func(ctx context.Context) error + done bool + success bool +} + +// Execute runs the executor's callback function. +func (exec *executorImpl) Execute() error { + if exec.done { + return ErrAlreadyDone + } + + // Verify the executor is r/gov/dao + assertCalledByGovdao() + + var err error + if exec.callback != nil { + err = exec.callback() + } else if exec.callbackCtx != nil { + ctx := context.WithValue(context.Empty(), statusContextKey, approvedStatus) + err = exec.callbackCtx(ctx) + } + exec.done = true + exec.success = err == nil + + return err +} + +// IsDone returns whether the executor has been executed. +func (exec *executorImpl) IsDone() bool { + return exec.done +} + +// IsSuccessful returns whether the execution was successful. +func (exec *executorImpl) IsSuccessful() bool { + return exec.success +} + +// IsExpired returns whether the execution had expired or not. +// This implementation never expires. +func (exec *executorImpl) IsExpired() bool { + return false +} + +func IsApprovedByGovdaoContext(ctx context.Context) bool { + v := ctx.Value(statusContextKey) + if v == nil { + return false + } + vs, ok := v.(string) + return ok && vs == approvedStatus +} + +func AssertContextApprovedByGovDAO(ctx context.Context) { + if !IsApprovedByGovdaoContext(ctx) { + panic("not approved by govdao") + } +} + +// assertCalledByGovdao asserts that the calling Realm is /r/gov/dao +func assertCalledByGovdao() { + caller := std.CurrentRealm().PkgPath() + + if caller != daoPkgPath { + panic(errNotGovDAO) + } +} + +type propContextKey string + +func (k propContextKey) String() string { return string(k) } + +const ( + statusContextKey = propContextKey("govdao-prop-status") + approvedStatus = "approved" +) diff --git a/examples/gno.land/p/gov/proposal/proposal_test.gno b/examples/gno.land/p/gov/proposal/proposal_test.gno new file mode 100644 index 00000000000..536871e644d --- /dev/null +++ b/examples/gno.land/p/gov/proposal/proposal_test.gno @@ -0,0 +1,156 @@ +package proposal + +import ( + "errors" + "std" + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestExecutor(t *testing.T) { + t.Parallel() + + verifyProposalFailed := func(e Executor) { + uassert.True(t, e.IsDone(), "expected proposal to be done") + uassert.False(t, e.IsSuccessful(), "expected proposal to fail") + } + + verifyProposalSucceeded := func(e Executor) { + uassert.True(t, e.IsDone(), "expected proposal to be done") + uassert.True(t, e.IsSuccessful(), "expected proposal to be successful") + } + + t.Run("govdao not caller", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func() error { + called = true + + return nil + } + ) + + // Create the executor + e := NewExecutor(cb) + + urequire.False(t, e.IsDone(), "expected status to be NotExecuted") + + // Execute as not the /r/gov/dao caller + uassert.PanicsWithMessage(t, errNotGovDAO.Error(), func() { + _ = e.Execute() + }) + + uassert.False(t, called, "expected proposal to not execute") + }) + + t.Run("execution successful", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func() error { + called = true + + return nil + } + ) + + // Create the executor + e := NewExecutor(cb) + + urequire.False(t, e.IsDone(), "expected status to be NotExecuted") + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + uassert.NotPanics(t, func() { + err := e.Execute() + + uassert.NoError(t, err) + }) + + uassert.True(t, called, "expected proposal to execute") + + // Make sure the execution params are correct + verifyProposalSucceeded(e) + }) + + t.Run("execution unsuccessful", func(t *testing.T) { + t.Parallel() + + var ( + called = false + expectedErr = errors.New("unexpected") + + cb = func() error { + called = true + + return expectedErr + } + ) + + // Create the executor + e := NewExecutor(cb) + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + uassert.NotPanics(t, func() { + err := e.Execute() + + uassert.ErrorIs(t, err, expectedErr) + }) + + uassert.True(t, called, "expected proposal to execute") + + // Make sure the execution params are correct + verifyProposalFailed(e) + }) + + t.Run("proposal already executed", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func() error { + called = true + + return nil + } + ) + + // Create the executor + e := NewExecutor(cb) + + urequire.False(t, e.IsDone(), "expected status to be NotExecuted") + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + uassert.NotPanics(t, func() { + uassert.NoError(t, e.Execute()) + }) + + uassert.True(t, called, "expected proposal to execute") + + // Make sure the execution params are correct + verifyProposalSucceeded(e) + + // Attempt to execute the proposal again + uassert.NotPanics(t, func() { + err := e.Execute() + + uassert.ErrorIs(t, err, ErrAlreadyDone) + }) + }) +} diff --git a/examples/gno.land/p/gov/proposal/types.gno b/examples/gno.land/p/gov/proposal/types.gno new file mode 100644 index 00000000000..6cd2da9ccfe --- /dev/null +++ b/examples/gno.land/p/gov/proposal/types.gno @@ -0,0 +1,37 @@ +// Package proposal defines types for proposal execution. +package proposal + +import "errors" + +// Executor represents a minimal closure-oriented proposal design. +// It is intended to be used by a govdao governance proposal (v1, v2, etc). +type Executor interface { + // Execute executes the given proposal, and returns any error encountered + // during the execution + Execute() error + + // IsDone returns a flag indicating if the proposal was executed + IsDone() bool + + // IsSuccessful returns a flag indicating if the proposal was executed + // and is successful + IsSuccessful() bool // IsDone() && !err + + // IsExpired returns whether the execution had expired or not. + IsExpired() bool +} + +// ErrAlreadyDone is the error returned when trying to execute an already +// executed proposal. +var ErrAlreadyDone = errors.New("already executed") + +// Status enum. +type Status string + +const ( + NotExecuted Status = "not_executed" + Succeeded Status = "succeeded" + Failed Status = "failed" +) + +const daoPkgPath = "gno.land/r/gov/dao" // TODO: make sure this is configurable through r/sys/vars diff --git a/examples/gno.land/p/nt/poa/gno.mod b/examples/gno.land/p/nt/poa/gno.mod new file mode 100644 index 00000000000..5c1b75eb05a --- /dev/null +++ b/examples/gno.land/p/nt/poa/gno.mod @@ -0,0 +1,10 @@ +module gno.land/p/nt/poa + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest +) diff --git a/examples/gno.land/p/nt/poa/option.gno b/examples/gno.land/p/nt/poa/option.gno new file mode 100644 index 00000000000..051ab2611f1 --- /dev/null +++ b/examples/gno.land/p/nt/poa/option.gno @@ -0,0 +1,14 @@ +package poa + +import "gno.land/p/sys/validators" + +type Option func(*PoA) + +// WithInitialSet sets the initial PoA validator set +func WithInitialSet(validators []validators.Validator) Option { + return func(p *PoA) { + for _, validator := range validators { + p.validators.Set(validator.Address.String(), validator) + } + } +} diff --git a/examples/gno.land/p/nt/poa/poa.gno b/examples/gno.land/p/nt/poa/poa.gno new file mode 100644 index 00000000000..1eab427f642 --- /dev/null +++ b/examples/gno.land/p/nt/poa/poa.gno @@ -0,0 +1,106 @@ +package poa + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/sys/validators" +) + +var ErrInvalidVotingPower = errors.New("invalid voting power") + +// PoA specifies the Proof of Authority validator set, with simple add / remove constraints. +// +// To add: +// - proposed validator must not be part of the set already +// - proposed validator voting power must be > 0 +// +// To remove: +// - proposed validator must be part of the set already +type PoA struct { + validators *avl.Tree // std.Address -> validators.Validator +} + +// NewPoA creates a new empty Proof of Authority validator set +func NewPoA(opts ...Option) *PoA { + // Create the empty set + p := &PoA{ + validators: avl.NewTree(), + } + + // Apply the options + for _, opt := range opts { + opt(p) + } + + return p +} + +func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) { + // Validate that the operation is a valid call. + // Check if the validator is already in the set + if p.IsValidator(address) { + return validators.Validator{}, validators.ErrValidatorExists + } + + // Make sure the voting power > 0 + if power == 0 { + return validators.Validator{}, ErrInvalidVotingPower + } + + v := validators.Validator{ + Address: address, + PubKey: pubKey, // TODO: in the future, verify the public key + VotingPower: power, + } + + // Add the validator to the set + p.validators.Set(address.String(), v) + + return v, nil +} + +func (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) { + // Validate that the operation is a valid call + // Fetch the validator + validator, err := p.GetValidator(address) + if err != nil { + return validators.Validator{}, err + } + + // Remove the validator from the set + p.validators.Remove(address.String()) + + return validator, nil +} + +func (p *PoA) IsValidator(address std.Address) bool { + _, exists := p.validators.Get(address.String()) + + return exists +} + +func (p *PoA) GetValidator(address std.Address) (validators.Validator, error) { + validatorRaw, exists := p.validators.Get(address.String()) + if !exists { + return validators.Validator{}, validators.ErrValidatorMissing + } + + validator := validatorRaw.(validators.Validator) + + return validator, nil +} + +func (p *PoA) GetValidators() []validators.Validator { + vals := make([]validators.Validator, 0, p.validators.Size()) + + p.validators.Iterate("", "", func(_ string, value interface{}) bool { + validator := value.(validators.Validator) + vals = append(vals, validator) + + return false + }) + + return vals +} diff --git a/examples/gno.land/p/nt/poa/poa_test.gno b/examples/gno.land/p/nt/poa/poa_test.gno new file mode 100644 index 00000000000..dfec387774c --- /dev/null +++ b/examples/gno.land/p/nt/poa/poa_test.gno @@ -0,0 +1,237 @@ +package poa + +import ( + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" + "gno.land/p/sys/validators" + + "gno.land/p/demo/ufmt" +) + +// generateTestValidators generates a dummy validator set +func generateTestValidators(count int) []validators.Validator { + vals := make([]validators.Validator, 0, count) + + for i := 0; i < count; i++ { + val := validators.Validator{ + Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), + PubKey: "public-key", + VotingPower: 1, + } + + vals = append(vals, val) + } + + return vals +} + +func TestPoA_AddValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("validator already in set", func(t *testing.T) { + t.Parallel() + + var ( + proposalAddress = testutils.TestAddress("caller") + proposalKey = "public-key" + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = proposalAddress + initialSet[0].PubKey = proposalKey + + // Create the protocol with an initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Attempt to add the validator + _, err := p.AddValidator(proposalAddress, proposalKey, 1) + uassert.ErrorIs(t, err, validators.ErrValidatorExists) + }) + + t.Run("invalid voting power", func(t *testing.T) { + t.Parallel() + + var ( + proposalAddress = testutils.TestAddress("caller") + proposalKey = "public-key" + ) + + // Create the protocol with no initial set + p := NewPoA() + + // Attempt to add the validator + _, err := p.AddValidator(proposalAddress, proposalKey, 0) + uassert.ErrorIs(t, err, ErrInvalidVotingPower) + }) +} + +func TestPoA_AddValidator(t *testing.T) { + t.Parallel() + + var ( + proposalAddress = testutils.TestAddress("caller") + proposalKey = "public-key" + ) + + // Create the protocol with no initial set + p := NewPoA() + + // Attempt to add the validator + _, err := p.AddValidator(proposalAddress, proposalKey, 1) + uassert.NoError(t, err) + + // Make sure the validator is added + if !p.IsValidator(proposalAddress) || p.validators.Size() != 1 { + t.Fatal("address is not validator") + } +} + +func TestPoA_RemoveValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("proposed removal not in set", func(t *testing.T) { + t.Parallel() + + var ( + proposalAddress = testutils.TestAddress("caller") + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = proposalAddress + + // Create the protocol with an initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Attempt to remove the validator + _, err := p.RemoveValidator(testutils.TestAddress("totally random")) + uassert.ErrorIs(t, err, validators.ErrValidatorMissing) + }) +} + +func TestPoA_RemoveValidator(t *testing.T) { + t.Parallel() + + var ( + proposalAddress = testutils.TestAddress("caller") + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = proposalAddress + + // Create the protocol with an initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Attempt to remove the validator + _, err := p.RemoveValidator(proposalAddress) + urequire.NoError(t, err) + + // Make sure the validator is removed + if p.IsValidator(proposalAddress) || p.validators.Size() != 0 { + t.Fatal("address is validator") + } +} + +func TestPoA_GetValidator(t *testing.T) { + t.Parallel() + + t.Run("validator not in set", func(t *testing.T) { + t.Parallel() + + // Create the protocol with no initial set + p := NewPoA() + + // Attempt to get the voting power + _, err := p.GetValidator(testutils.TestAddress("caller")) + uassert.ErrorIs(t, err, validators.ErrValidatorMissing) + }) + + t.Run("validator fetched", func(t *testing.T) { + t.Parallel() + + var ( + address = testutils.TestAddress("caller") + pubKey = "public-key" + votingPower = uint64(10) + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = address + initialSet[0].PubKey = pubKey + initialSet[0].VotingPower = votingPower + + // Create the protocol with an initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Get the validator + val, err := p.GetValidator(address) + urequire.NoError(t, err) + + // Validate the address + if val.Address != address { + t.Fatal("invalid address") + } + + // Validate the voting power + if val.VotingPower != votingPower { + t.Fatal("invalid voting power") + } + + // Validate the public key + if val.PubKey != pubKey { + t.Fatal("invalid public key") + } + }) +} + +func TestPoA_GetValidators(t *testing.T) { + t.Parallel() + + t.Run("empty set", func(t *testing.T) { + t.Parallel() + + // Create the protocol with no initial set + p := NewPoA() + + // Attempt to get the voting power + vals := p.GetValidators() + + if len(vals) != 0 { + t.Fatal("validator set is not empty") + } + }) + + t.Run("validator set fetched", func(t *testing.T) { + t.Parallel() + + initialSet := generateTestValidators(10) + + // Create the protocol with an initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Get the validator set + vals := p.GetValidators() + + if len(vals) != len(initialSet) { + t.Fatal("returned validator set mismatch") + } + + for _, val := range vals { + for _, initialVal := range initialSet { + if val.Address != initialVal.Address { + continue + } + + // Validate the voting power + uassert.Equal(t, val.VotingPower, initialVal.VotingPower) + + // Validate the public key + uassert.Equal(t, val.PubKey, initialVal.PubKey) + } + } + }) +} diff --git a/examples/gno.land/p/sys/validators/gno.mod b/examples/gno.land/p/sys/validators/gno.mod new file mode 100644 index 00000000000..9c7a38aada0 --- /dev/null +++ b/examples/gno.land/p/sys/validators/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sys/validators diff --git a/examples/gno.land/p/sys/validators/types.gno b/examples/gno.land/p/sys/validators/types.gno new file mode 100644 index 00000000000..bd7d5df2ba8 --- /dev/null +++ b/examples/gno.land/p/sys/validators/types.gno @@ -0,0 +1,51 @@ +package validators + +import ( + "errors" + "std" +) + +// ValsetProtocol defines the validator set protocol (PoA / PoS / PoC / ?) +type ValsetProtocol interface { + // AddValidator adds a new validator to the validator set. + // If the validator is already present, the method should error out + // + // TODO: This API is not ideal -- the address should be derived from + // the public key, and not be passed in as such, but currently Gno + // does not support crypto address derivation + AddValidator(address std.Address, pubKey string, power uint64) (Validator, error) + + // RemoveValidator removes the given validator from the set. + // If the validator is not present in the set, the method should error out + RemoveValidator(address std.Address) (Validator, error) + + // IsValidator returns a flag indicating if the given + // bech32 address is part of the validator set + IsValidator(address std.Address) bool + + // GetValidator returns the validator using the given address + GetValidator(address std.Address) (Validator, error) + + // GetValidators returns the currently active validator set + GetValidators() []Validator +} + +// Validator represents a single chain validator +type Validator struct { + Address std.Address // bech32 address + PubKey string // bech32 representation of the public key + VotingPower uint64 +} + +const ( + ValidatorAddedEvent = "ValidatorAdded" // emitted when a validator was added to the set + ValidatorRemovedEvent = "ValidatorRemoved" // emitted when a validator was removed from the set +) + +var ( + // ErrValidatorExists is returned when the validator is already in the set + ErrValidatorExists = errors.New("validator already exists") + + // ErrValidatorMissing is returned when the validator is not in the set + ErrValidatorMissing = errors.New("validator doesn't exist") +) diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index bc17ee9df3b..eb6f44d4026 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/art/gnoface require ( - gno.land/p/demo/rand v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 95493b52bf5..9e85c5c7387 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -1,16 +1,16 @@ package gnoface import ( + "math/rand" "std" "strconv" "strings" - "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := std.GetHeight() + seed := uint64(std.GetHeight()) path = strings.TrimSpace(path) if path != "" { @@ -18,7 +18,7 @@ func Render(path string) string { if err != nil { panic(err) } - seed = int64(s) + seed = uint64(s) } output := ufmt.Sprintf("Gnoface #%d\n", seed) @@ -26,7 +26,7 @@ func Render(path string) string { return output } -func Draw(seed int64) string { +func Draw(seed uint64) string { var ( hairs = []string{ " s", @@ -102,7 +102,7 @@ func Draw(seed int64) string { } ) - r := rand.FromSeed(seed) + r := rand.New(rand.NewPCG(seed, 0xdeadbeef)) return pick(r, hairs) + "\n" + pick(r, headtop) + "\n" + @@ -117,8 +117,8 @@ func Draw(seed int64) string { pick(r, headbottom) + "\n" } -func pick(r *rand.Instance, slice []string) string { - return slice[r.Intn(len(slice))] +func pick(r *rand.Rand, slice []string) string { + return slice[r.IntN(len(slice))] } // based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno index 630cce85c55..44c70ff12fb 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno @@ -3,26 +3,27 @@ package gnoface import ( "testing" + "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" ) func TestDraw(t *testing.T) { cases := []struct { - seed int64 + seed uint64 expected string }{ { seed: 42, expected: ` ||||||| - ////////\ + ||||||||| | | - | ~ . | -)| X X |. + | . ~ | +)| v v |O | | - | C | + | L | | | - | __/ | + | ___ | | | \~~~~~~~/ `[1:], @@ -30,31 +31,31 @@ func TestDraw(t *testing.T) { { seed: 1337, expected: ` - s - /|||||||\ + ....... + ||||||||| | | - | . * | -o| ~ ~ |. + | . _ | +D| x X |O | | - | O | + | ~ | | | - | __/ | + | ~~~ | | | - \_______/ + \~~~~~~~/ `[1:], }, { seed: 123456789, expected: ` - s - /~~~~~~~\ + ....... + ////////\ | | - | ~ . | -<| ~ ~ |< + | ~ * | +|| x X |o | | | V | | | - | \_/ | + | . | | | \-------/ `[1:], @@ -64,9 +65,7 @@ o| ~ ~ |. name := ufmt.Sprintf("%d", tc.seed) t.Run(name, func(t *testing.T) { got := Draw(tc.seed) - if got != tc.expected { - t.Errorf("got %s, expected %s", got, tc.expected) - } + uassert.Equal(t, string(tc.expected), got) }) } } @@ -80,14 +79,14 @@ func TestRender(t *testing.T) { path: "42", expected: "Gnoface #42\n```" + ` ||||||| - ////////\ + ||||||||| | | - | ~ . | -)| X X |. + | . ~ | +)| v v |O | | - | C | + | L | | | - | __/ | + | ___ | | | \~~~~~~~/ ` + "```\n", @@ -95,31 +94,31 @@ func TestRender(t *testing.T) { { path: "1337", expected: "Gnoface #1337\n```" + ` - s - /|||||||\ + ....... + ||||||||| | | - | . * | -o| ~ ~ |. + | . _ | +D| x X |O | | - | O | + | ~ | | | - | __/ | + | ~~~ | | | - \_______/ + \~~~~~~~/ ` + "```\n", }, { path: "123456789", expected: "Gnoface #123456789\n```" + ` - s - /~~~~~~~\ + ....... + ////////\ | | - | ~ . | -<| ~ ~ |< + | ~ * | +|| x X |o | | | V | | | - | \_/ | + | . | | | \-------/ ` + "```\n", @@ -128,9 +127,7 @@ o| ~ ~ |. for _, tc := range cases { t.Run(tc.path, func(t *testing.T) { got := Render(tc.path) - if got != tc.expected { - t.Errorf("got %s, expected %s", got, tc.expected) - } + uassert.Equal(t, tc.expected, got) }) } } diff --git a/examples/gno.land/r/demo/art/millipede/gno.mod b/examples/gno.land/r/demo/art/millipede/gno.mod index 346e3a1673c..7cd604206fa 100644 --- a/examples/gno.land/r/demo/art/millipede/gno.mod +++ b/examples/gno.land/r/demo/art/millipede/gno.mod @@ -1,3 +1,6 @@ module gno.land/r/demo/art/millipede -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/art/millipede/millipede.gno b/examples/gno.land/r/demo/art/millipede/millipede.gno index dbffd8b3fbb..414941b947b 100644 --- a/examples/gno.land/r/demo/art/millipede/millipede.gno +++ b/examples/gno.land/r/demo/art/millipede/millipede.gno @@ -40,10 +40,10 @@ func Render(path string) string { output := "```\n" + Draw(size) + "```\n" if size > minSize { - output += ufmt.Sprintf("[%d](/r/art/millpede:%d)< ", size-1, size-1) + output += ufmt.Sprintf("[%d](/r/demo/art/millpede:%d)< ", size-1, size-1) } if size < maxSize { - output += ufmt.Sprintf(" >[%d](/r/art/millipede:%d)", size+1, size+1) + output += ufmt.Sprintf(" >[%d](/r/demo/art/millipede:%d)", size+1, size+1) } return output } diff --git a/examples/gno.land/r/demo/art/millipede/millipede_test.gno b/examples/gno.land/r/demo/art/millipede/millipede_test.gno index 38914707b4a..7fb5a5114b9 100644 --- a/examples/gno.land/r/demo/art/millipede/millipede_test.gno +++ b/examples/gno.land/r/demo/art/millipede/millipede_test.gno @@ -1,8 +1,9 @@ package millipede import ( - "fmt" "testing" + + "gno.land/p/demo/uassert" ) func TestRender(t *testing.T) { @@ -34,7 +35,7 @@ func TestRender(t *testing.T) { ╚═(███)═╝ ╚═(███)═╝ ╚═(███)═╝ -` + "```\n[19](/r/art/millpede:19)< >[21](/r/art/millipede:21)", +` + "```\n[19](/r/demo/art/millpede:19)< >[21](/r/demo/art/millipede:21)", }, { path: "4", @@ -44,16 +45,14 @@ func TestRender(t *testing.T) { ╚═(███)═╝ ╚═(███)═╝ ╚═(███)═╝ -` + "```\n[3](/r/art/millpede:3)< >[5](/r/art/millipede:5)", +` + "```\n[3](/r/demo/art/millpede:3)< >[5](/r/demo/art/millipede:5)", }, } for _, tc := range cases { t.Run(tc.path, func(t *testing.T) { got := Render(tc.path) - if got != tc.expected { - t.Errorf("expected %s, got %s.", tc.expected, got) - } + uassert.Equal(t, tc.expected, got) }) } } diff --git a/examples/gno.land/r/demo/banktest/README.md b/examples/gno.land/r/demo/banktest/README.md index 4182bfcfe0c..944757f9b12 100644 --- a/examples/gno.land/r/demo/banktest/README.md +++ b/examples/gno.land/r/demo/banktest/README.md @@ -1,6 +1,6 @@ This is a simple test realm contract that demonstrates how to use the banker. -See [gno.land/r/banktest/banktest.go](/r/banktest/banktest.go) to see the original contract code. +See [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code. This article will go through each line to explain how it works. @@ -16,8 +16,7 @@ import ( ) ``` -The "std" package is defined by the gno code in stdlibs/std/.
-Self explanatory; and you'll see more usage from std later. +The "std" package is defined by the gno code in stdlibs/std/.
Self explanatory; and you'll see more usage from std later. ```go type activity struct { @@ -37,14 +36,9 @@ func (act *activity) String() string { var latest [10]*activity ``` -This is just maintaining a list of recent activity to this contract. -Notice that the "latest" variable is defined "globally" within -the context of the realm with path "gno.land/r/banktest". +This is just maintaining a list of recent activity to this contract. Notice that the "latest" variable is defined "globally" within the context of the realm with path "gno.land/r/demo/banktest". -This means that calls to functions defined within this package -are encapsulated within this "data realm", where the data is -mutated based on transactions that can potentially cross many -realm and non-realm package boundaries (in the call stack). +This means that calls to functions defined within this package are encapsulated within this "data realm", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack). ```go // Deposit will take the coins (to the realm's pkgaddr) or return them to user. @@ -54,11 +48,7 @@ func Deposit(returnDenom string, returnAmount int64) string { send := std.Coins{{returnDenom, returnAmount}} ``` -This is the beginning of the definition of the contract function named -"Deposit". `std.AssertOriginCall() asserts that this function was called by a -gno transactional Message. The caller is the user who signed off on this -transactional message. Send is the amount of deposit sent along with this -message. +This is the beginning of the definition of the contract function named "Deposit". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message. ```go // record activity @@ -74,7 +64,7 @@ message. latest[0] = act ``` -Updating the "latest" array for viewing at gno.land/r/banktest: (w/ trailing colon). +Updating the "latest" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon). ```go // return if any. @@ -98,8 +88,7 @@ use a std.Banker instance to return any deposited coins to the original sender. Notice that each realm package has an associated Cosmos address. - -Finally, the results are rendered via an ABCI query call when you visit [/r/banktest:](/r/banktest:). +Finally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:). ```go func Render(path string) string { @@ -124,4 +113,4 @@ func Render(path string) string { } ``` -You can call this contract yourself, by vistiing [/r/banktest](/r/banktest) and the [quickstart guide](/r/boards:gnolang/4). +You can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4). diff --git a/examples/gno.land/r/demo/banktest/banktest.gno b/examples/gno.land/r/demo/banktest/banktest.gno index 1a23838dcfc..29c479dd025 100644 --- a/examples/gno.land/r/demo/banktest/banktest.gno +++ b/examples/gno.land/r/demo/banktest/banktest.gno @@ -40,7 +40,7 @@ func Deposit(returnDenom string, returnAmount int64) string { // return if any. if returnAmount > 0 { banker := std.GetBanker(std.BankerTypeOrigSend) - pkgaddr := std.CurrentRealm().Addr() + pkgaddr := std.GetOrigPkgAddr() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" 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 c997d92c53a..4ea76bbe17a 100644 --- a/examples/gno.land/r/demo/banktest/z_0_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_0_filetest.gno @@ -1,8 +1,7 @@ -package main - -// NOTE: this doesn't do anything, as it sends to "main". // SEND: 100000000ugnot +package main + import ( "std" @@ -10,38 +9,40 @@ import ( ) func main() { + // set up main address and banktest addr. banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") - - // print main balance before. mainaddr := std.DerivePkgAddr("main") std.TestSetOrigCaller(mainaddr) + std.TestSetOrigPkgAddr(banktestAddr) - banker := std.GetBanker(std.BankerTypeReadonly) + // get and print balance of mainaddr. + // with the SEND, + 200 gnot given by the TestContext, main should have 300gnot. + banker := std.GetBanker(std.BankerTypeRealmSend) mainbal := banker.GetCoins(mainaddr) - println("main before:", mainbal) // plus OrigSend equals 300. + println("main before:", mainbal) - // simulate a Deposit call. - std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) - res := banktest.Deposit("ugnot", 100000000) + // simulate a Deposit call. use Send + OrigSend to simulate -send. + banker.SendCoins(mainaddr, banktestAddr, std.Coins{{"ugnot", 100_000_000}}) + std.TestSetOrigSend(std.Coins{{"ugnot", 100_000_000}}, nil) + res := banktest.Deposit("ugnot", 50_000_000) println("Deposit():", res) // print main balance after. mainbal = banker.GetCoins(mainaddr) - println("main after:", mainbal) // still 300. + println("main after:", mainbal) - // simulate a Render(). + // simulate a Render(). banker should have given back all coins. res = banktest.Render("") println(res) } // Output: -// main before: 200000000ugnot +// main before: 300000000ugnot // Deposit(): returned! -// main after: 300000000ugnot +// main after: 250000000ugnot // ## recent activity // -// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 100000000ugnot returned, at 2009-02-13 11:31pm UTC +// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC // // ## total deposits -// 300000000ugnot +// 50000000ugnot 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 3c25a148c35..8f9f7647036 100644 --- a/examples/gno.land/r/demo/banktest/z_1_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_1_filetest.gno @@ -10,6 +10,7 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") // simulate a Deposit call. + std.TestSetOrigPkgAddr(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 101000000) diff --git a/examples/gno.land/r/demo/banktest/z_3_filetest.gno b/examples/gno.land/r/demo/banktest/z_3_filetest.gno new file mode 100644 index 00000000000..ca8717dfcc9 --- /dev/null +++ b/examples/gno.land/r/demo/banktest/z_3_filetest.gno @@ -0,0 +1,20 @@ +package main + +import ( + "std" +) + +func main() { + banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") + + mainaddr := std.DerivePkgAddr("main") + std.TestSetOrigCaller(mainaddr) + + banker := std.GetBanker(std.BankerTypeRealmSend) + send := std.Coins{{"ugnot", 123}} + banker.SendCoins(banktestAddr, mainaddr, send) + +} + +// Error: +// can only send coins from realm that created banker "g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4", not "g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz" diff --git a/examples/gno.land/r/demo/bar20/bar20.gno b/examples/gno.land/r/demo/bar20/bar20.gno new file mode 100644 index 00000000000..1d6ecd3d378 --- /dev/null +++ b/examples/gno.land/r/demo/bar20/bar20.gno @@ -0,0 +1,46 @@ +// Package bar20 is similar to gno.land/r/demo/foo20 but exposes a safe-object +// that can be used by `maketx run`, another contract importing foo20, and in +// the future when we'll support `maketx call Token.XXX`. +package bar20 + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" +) + +var ( + banker *grc20.Banker // private banker. + Token grc20.Token // public safe-object. +) + +func init() { + banker = grc20.NewBanker("Bar", "BAR", 4) + Token = banker.Token() +} + +func Faucet() string { + caller := std.PrevRealm().Addr() + if err := banker.Mint(caller, 1_000_000); err != nil { + return "error: " + err.Error() + } + return "OK" +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return banker.RenderHome() // XXX: should be Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := std.Address(parts[1]) + balance := Token.BalanceOf(owner) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} diff --git a/examples/gno.land/r/demo/bar20/bar20_test.gno b/examples/gno.land/r/demo/bar20/bar20_test.gno new file mode 100644 index 00000000000..20349258c1b --- /dev/null +++ b/examples/gno.land/r/demo/bar20/bar20_test.gno @@ -0,0 +1,19 @@ +package bar20 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +func TestPackage(t *testing.T) { + alice := testutils.TestAddress("alice") + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // XXX: should not need this + + urequire.Equal(t, Token.BalanceOf(alice), uint64(0)) + urequire.Equal(t, Faucet(), "OK") + urequire.Equal(t, Token.BalanceOf(alice), uint64(1_000_000)) +} diff --git a/examples/gno.land/r/demo/bar20/gno.mod b/examples/gno.land/r/demo/bar20/gno.mod new file mode 100644 index 00000000000..2ec82d7be0b --- /dev/null +++ b/examples/gno.land/r/demo/bar20/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/bar20 + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/boards/README.md b/examples/gno.land/r/demo/boards/README.md index 849998b04e9..a9b68ec9c92 100644 --- a/examples/gno.land/r/demo/boards/README.md +++ b/examples/gno.land/r/demo/boards/README.md @@ -8,7 +8,7 @@ name ["gno.land/r/demo/boards"](https://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 test3.gno.land:36657` if you have $GNOT on the testnet. +with `-remote test3.gno.land:26657` if you have $GNOT on the testnet. (To use the testnet, also replace `-chainid dev` with `-chainid test3` .) ### Build `gnokey` (and other tools). @@ -58,7 +58,7 @@ your `ACCOUNT_ADDR` and `KEYNAME` 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/gno.land/cmd/gnofaucet/README.md +See this page: https://github.com/gnolang/gno/blob/master/gno.land/cmd/gnofaucet/README.md ### Start the `gnoland` node. @@ -97,8 +97,7 @@ Interactive documentation: https://test3.gno.land/r/demo/boards?help&__func=Crea 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 +./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. @@ -120,8 +119,7 @@ Interactive documentation: https://test3.gno.land/r/demo/boards?help&__func=Crea Interactive documentation: https://test3.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 +./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:BOARDNAME/1" -remote localhost:26657 ``` ### Render page with optional path expression. @@ -130,8 +128,7 @@ The contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/b the `Render(path string)` function like so: ```bash -./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards -gnolang" +./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:gnolang" ``` ## View the board in the browser. diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 6643c863cec..1d26126fcb2 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -17,7 +17,9 @@ func GetBoardIDFromName(name string) (BoardID, bool) { } func CreateBoard(name string) BoardID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } bid := incGetBoardID() caller := std.GetOrigCaller() if usernameOf(caller) == "" { @@ -33,7 +35,7 @@ func CreateBoard(name string) BoardID { func checkAnonFee() bool { sent := std.GetOrigSend() - anonFeeCoin := std.Coin{"ugnot", int64(gDefaultAnonFee)} + anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { return true } @@ -41,7 +43,9 @@ func checkAnonFee() bool { } func CreateThread(bid BoardID, title string, body string) PostID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() if usernameOf(caller) == "" { if !checkAnonFee() { @@ -57,7 +61,9 @@ func CreateThread(bid BoardID, title string, body string) PostID { } func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() if usernameOf(caller) == "" { if !checkAnonFee() { @@ -85,7 +91,9 @@ 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 { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() if usernameOf(caller) == "" { // TODO: allow with gDefaultAnonFee payment. @@ -113,7 +121,9 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar } func DeletePost(bid BoardID, threadid, postid PostID, reason string) { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() board := getBoard(bid) if board == nil { @@ -143,7 +153,9 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { } func EditPost(bid BoardID, threadid, postid PostID, title, body string) { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() board := getBoard(bid) if board == nil { 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 872d01dd793..5e8ff520a54 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test import ( 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 index 9ada8abd0c0..9bcbe9ffafa 100644 --- a/examples/gno.land/r/demo/boards/z_0_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_b_filetest.gno @@ -1,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 199000000ugnot +// SEND: 19900000ugnot import ( "gno.land/r/demo/boards" @@ -20,4 +20,4 @@ func main() { } // Error: -// payment must not be less than 200000000 +// 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 36b7bcfda04..99fd339aed8 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 2e9e3f1436f..c77e60e3f3a 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 a06713fcbe3..6db036e87ba 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 f5611d3d6c3..e20964d50b7 100644 --- a/examples/gno.land/r/demo/boards/z_0_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_filetest.gno @@ -1,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 20000000ugnot import ( "gno.land/r/demo/boards" 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 8ec2e45421a..ad57283bfcf 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 0981d85a57f..cf8a332174f 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,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 51c597d7998..8555af0b576 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 f32d69c8056..548b5865f65 100644 --- a/examples/gno.land/r/demo/boards/z_10_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 fd62a884efa..d7dc7b90782 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,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 63907146f82..3aa28095502 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,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 ba40fa39021..df764303562 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,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 b3803c505f2..c114e769ab1 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,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 937673c8bd4..4cbdeeca4c3 100644 --- a/examples/gno.land/r/demo/boards/z_11_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_filetest.gno @@ -1,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 82fc4b44a8c..909be880efa 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 af2f66594db..6b2166895c0 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 6cb6d34bf49..7397c487d7d 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 eb21e8dd5ba..37b6473f7ac 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,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 59ab07d17d8..4ea75b27753 100644 --- a/examples/gno.land/r/demo/boards/z_12_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 b5c7bb863e1..ba0a277e2f1 100644 --- a/examples/gno.land/r/demo/boards/z_1_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_1_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 2a6b937c875..f0d53204e38 100644 --- a/examples/gno.land/r/demo/boards/z_2_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_2_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 0d25dae08ae..021ae10b825 100644 --- a/examples/gno.land/r/demo/boards/z_3_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_3_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 2acafe05470..f0620c28c9d 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot @@ -48,83 +48,26 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/users"] // switchrealm["gno.land/r/demo/boards"] -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:101]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000003" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:102" -// } -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:101", -// "ModTime": "109", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "ModTime": "123", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "68663c8895d37d479e417c11e21badfe21345c61", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:110]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={ // "Fields": [ // { // "T": { @@ -146,19 +89,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" -// } -// } +// "TV": null // } // }, // { @@ -194,13 +131,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:110", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "3f34ac77289aa1d5f9a2f8b6d083138325816fb0", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={ // "Fields": [ // { // "T": { @@ -237,19 +193,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "94a6665a44bac6ede7f3e3b87173e537b12f9532", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "b58581159917d8d7ad0992009d7184fc8ca00fcc", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:101" -// } -// } +// "TV": null // } // }, // { @@ -262,30 +212,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "bc8e5b4e782a0bbc4ac9689681f119beb7b34d59", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fb593e86d35aaf607e0d21e6bd4f84519c44585f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:110" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:96", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "9957eadbc91dd32f33b0d815e041a32dbdea0671", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={ // "Fields": [ // { // "T": { @@ -298,13 +261,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:113]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={ // "Fields": [ // { // "T": { @@ -317,13 +280,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:113", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:114]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={ // "Fields": [ // { // "T": { @@ -336,13 +299,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:114", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={ // "Fields": [ // { // "N": "AAAAgJSeXbo=", @@ -373,19 +336,19 @@ func main() { // "Escaped": true, // "ObjectID": "336074805fc853987abe6f7fe3ad97a6a6f3077a:2" // }, -// "Index": "189", +// "Index": "182", // "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:116]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={ // "Fields": [ // { // "T": { @@ -410,13 +373,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:116", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={ // "Fields": [ // { // "T": { @@ -428,19 +391,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Board" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:81" -// } -// } +// "TV": null // } // }, // { @@ -487,8 +444,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "130542396d7549d1d516a3ef4a63bb44ef3da06f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112" +// "Hash": "f91e355bd19240f0f3350a7fa0e6a82b72225916", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128" // } // }, // { @@ -498,8 +455,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "80acd8746478317194b8546170335c796a4dfb3f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:113" +// "Hash": "9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129" // } // }, // { @@ -509,8 +466,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "c1a8f769f3b9d52dd38ac4759116edaca287636f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:114" +// "Hash": "eb768b0140a5fe95f9c58747f0960d647dacfd42", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130" // } // }, // { @@ -540,8 +497,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "25ffc45509708ca0ae17271cb4c3a1dfb367b965", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115" +// "Hash": "0fd3352422af0a56a77ef2c9e88f479054e3d51f", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131" // } // }, // { @@ -551,95 +508,57 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "c3a60b602b564d07677a212372f4ac1cae4270fd", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:116" +// "Hash": "bed4afa8ffdbbf775451c947fc68b27a345ce32a", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132" // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", // "IsEscaped": true, // "ModTime": "0", // "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Post" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "c45bbd47a46681a63af973db0ec2180922e4a8ae", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127" +// } // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:108]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000003" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:102" -// } -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={ // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:108", -// "ModTime": "117", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120", +// "ModTime": "134", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "dc1f011553dc53e7a846049e08cc77fa35ea6a51", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:118]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={ // "Fields": [ // { // "T": { @@ -661,19 +580,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" -// } -// } +// "TV": null // } // }, // { @@ -709,13 +622,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:118", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "96b86b4585c7f1075d7794180a5581f72733a7ab", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={ // "Fields": [ // { // "T": { @@ -752,19 +684,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "32274e1f28fb2b97d67a1262afd362d370de7faa", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6a86bc7763703c8f2b9d286368921159d6db121c", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:108" -// } -// } +// "TV": null // } // }, // { @@ -777,30 +703,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "52faa8a2dfefd4b6b6249eff2f9c123ad455e81d", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:118" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:97", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", // "RefCount": "1" // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:81]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "5cb875179e86d32c517322af7a323b2a5f3e6cc5", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134" +// } +// } +// } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={ // "Fields": [ // { // "N": "AQAAAAAAAAA=", @@ -846,8 +785,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "a8e67b9881af89ca2ec2f05778bf7528a54a5833", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:82" +// "Hash": "a416a751c3a45a1e5cba11e737c51340b081e372", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86" // } // }, // { @@ -864,8 +803,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "d8ae14a4620e3c6dedabd76cd0c5d7e3c205d647", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:83" +// "Hash": "36299fccbc13f2a84c4629fad4cb940f0bd4b1c6", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87" // } // }, // { @@ -875,19 +814,19 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "edb1857302fa916c562cd077cdf2a3626e29ae2b", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84" +// "Hash": "af6ed0268f99b7f369329094eb6dfaea7812708b", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88" // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:81", -// "IsEscaped": true, -// "ModTime": "108", -// "RefCount": "6" +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84", +// "RefCount": "1" // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:96]={ +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={ // "Fields": [ // { // "T": { @@ -899,30 +838,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "609e7f519c65f94503427a14f973b4b83989cdc8", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:96", -// "ModTime": "108", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:95", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", // "RefCount": "1" // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:97]={ +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={ // "Fields": [ // { // "T": { @@ -934,26 +867,20 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6760340f5b40e05221dc530940683b0b9a422503", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:97", -// "ModTime": "108", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:95", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", // "RefCount": "1" // } // } @@ -962,4 +889,4 @@ func main() { // switchrealm["gno.land/r/demo/users"] // switchrealm["gno.land/r/demo/users"] // switchrealm["gno.land/r/demo/boards"] -// switchrealm["gno.land/r/boards_test"] +// 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 105c7f19ef7..e79da5c3677 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: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" 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 bb9d7a57010..176b1d89015 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: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" 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 e54ac578dc1..54cfe49eec6 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: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" 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 76fb4ee432c..c326d961c91 100644 --- a/examples/gno.land/r/demo/boards/z_5_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 c1cd84d35a7..b7de2d08bf9 100644 --- a/examples/gno.land/r/demo/boards/z_6_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_6_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot 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 343ec8f909e..f1d41aa1723 100644 --- a/examples/gno.land/r/demo/boards/z_7_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_7_filetest.gno @@ -1,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "gno.land/r/demo/boards" 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 226dae62b3f..18ad64083f4 100644 --- a/examples/gno.land/r/demo/boards/z_8_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_8_filetest.gno @@ -1,7 +1,7 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 9ba9786c519..8d07ba0e710 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,11 +1,9 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot import ( - "strconv" - "gno.land/r/demo/boards" "gno.land/r/demo/users" ) 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 4d53f60f10f..68daf770b4f 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,9 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot import ( - "strconv" - "gno.land/r/demo/boards" "gno.land/r/demo/users" ) 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 f2703bc1622..10a1444fd35 100644 --- a/examples/gno.land/r/demo/boards/z_9_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/boards_test +// PKGPATH: gno.land/r/demo/boards_test package boards_test // SEND: 200000000ugnot diff --git a/examples/gno.land/r/demo/echo/echo_test.gno b/examples/gno.land/r/demo/echo/echo_test.gno index cafbfa65781..92f4868e245 100644 --- a/examples/gno.land/r/demo/echo/echo_test.gno +++ b/examples/gno.land/r/demo/echo/echo_test.gno @@ -2,13 +2,11 @@ package echo import ( "testing" + + "gno.land/p/demo/urequire" ) func Test(t *testing.T) { - if Render("aa") != "aa" { - t.Fail() - } - if Render("") != "" { - t.Fail() - } + urequire.Equal(t, "aa", Render("aa")) + urequire.Equal(t, "", Render("")) } diff --git a/examples/gno.land/r/demo/echo/gno.mod b/examples/gno.land/r/demo/echo/gno.mod index f07d78943d1..4ca5ccab6e0 100644 --- a/examples/gno.land/r/demo/echo/gno.mod +++ b/examples/gno.land/r/demo/echo/gno.mod @@ -1 +1,3 @@ module gno.land/r/demo/echo + +require gno.land/p/demo/urequire v0.0.0-latest diff --git a/examples/gno.land/r/demo/event/event.gno b/examples/gno.land/r/demo/event/event.gno new file mode 100644 index 00000000000..9e5de540734 --- /dev/null +++ b/examples/gno.land/r/demo/event/event.gno @@ -0,0 +1,9 @@ +package event + +import ( + "std" +) + +func Emit(value string) { + std.Emit("TAG", "key", value) +} diff --git a/examples/gno.land/r/demo/event/gno.mod b/examples/gno.land/r/demo/event/gno.mod new file mode 100644 index 00000000000..64987d43d79 --- /dev/null +++ b/examples/gno.land/r/demo/event/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/event diff --git a/examples/gno.land/r/demo/foo20/foo20.gno b/examples/gno.land/r/demo/foo20/foo20.gno index 162454800e8..9d4e5d40193 100644 --- a/examples/gno.land/r/demo/foo20/foo20.gno +++ b/examples/gno.land/r/demo/foo20/foo20.gno @@ -1,3 +1,5 @@ +// foo20 is a GRC20 token contract where all the GRC20 methods are proxified +// with top-level functions. see also gno.land/r/demo/bar20. package foo20 import ( @@ -5,127 +7,93 @@ import ( "strings" "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" - pusers "gno.land/p/demo/users" + "gno.land/r/demo/users" ) var ( - foo *grc20.AdminToken - admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // TODO: helper to change admin + banker *grc20.Banker + admin *ownable.Ownable + token grc20.Token ) func init() { - foo = grc20.NewAdminToken("Foo", "FOO", 4) - foo.Mint(admin, 1000000*10000) // @administrator (1M) - foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k) + admin = ownable.NewWithAddress("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") // @manfred + banker = grc20.NewBanker("Foo", "FOO", 4) + banker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M) + token = banker.Token() } -// method proxies as public functions. -// - -// getters. - -func TotalSupply() uint64 { - return foo.TotalSupply() -} +func TotalSupply() uint64 { return token.TotalSupply() } func BalanceOf(owner pusers.AddressOrName) uint64 { - balance, err := foo.BalanceOf(users.Resolve(owner)) - if err != nil { - panic(err) - } - return balance + ownerAddr := users.Resolve(owner) + return token.BalanceOf(ownerAddr) } func Allowance(owner, spender pusers.AddressOrName) uint64 { - allowance, err := foo.Allowance(users.Resolve(owner), users.Resolve(spender)) - if err != nil { - panic(err) - } - return allowance + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return token.Allowance(ownerAddr, spenderAddr) } -// setters. - func Transfer(to pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - err := foo.Transfer(caller, users.Resolve(to), amount) - if err != nil { - panic(err) - } + toAddr := users.Resolve(to) + checkErr(token.Transfer(toAddr, amount)) } func Approve(spender pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - err := foo.Approve(caller, users.Resolve(spender), amount) - if err != nil { - panic(err) - } + spenderAddr := users.Resolve(spender) + checkErr(token.Approve(spenderAddr, amount)) } func TransferFrom(from, to pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - err := foo.TransferFrom(caller, users.Resolve(from), users.Resolve(to), amount) - if err != nil { - panic(err) - } + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(token.TransferFrom(fromAddr, toAddr, amount)) } -// faucet. - +// 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() { - // FIXME: add limits? - // FIXME: add payment in gnot? caller := std.PrevRealm().Addr() - err := foo.Mint(caller, 1000*10000) // 1k - if err != nil { - panic(err) - } + amount := uint64(1_000 * 10_000) // 1k + checkErr(banker.Mint(caller, amount)) } -// administration. - -func Mint(address pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := foo.Mint(users.Resolve(address), amount) - if err != nil { - panic(err) - } +func Mint(to pusers.AddressOrName, amount uint64) { + admin.AssertCallerIsOwner() + toAddr := users.Resolve(to) + checkErr(banker.Mint(toAddr, amount)) } -func Burn(address pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := foo.Burn(users.Resolve(address), amount) - if err != nil { - panic(err) - } +func Burn(from pusers.AddressOrName, amount uint64) { + admin.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(banker.Burn(fromAddr, amount)) } -// render. -// - func Render(path string) string { parts := strings.Split(path, "/") c := len(parts) switch { case path == "": - return foo.RenderHome() + return banker.RenderHome() case c == 2 && parts[0] == "balance": owner := pusers.AddressOrName(parts[1]) - balance, _ := foo.BalanceOf(users.Resolve(owner)) + ownerAddr := users.Resolve(owner) + balance := banker.BalanceOf(ownerAddr) return ufmt.Sprintf("%d\n", balance) default: return "404\n" } } -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") +func checkErr(err error) { + if err != nil { + panic(err) } } diff --git a/examples/gno.land/r/demo/foo20/foo20_test.gno b/examples/gno.land/r/demo/foo20/foo20_test.gno index 38bdcb333c1..77c99d0525e 100644 --- a/examples/gno.land/r/demo/foo20/foo20_test.gno +++ b/examples/gno.land/r/demo/foo20/foo20_test.gno @@ -4,14 +4,18 @@ import ( "std" "testing" + "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) { - admin := pusers.AddressOrName("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - manfred := pusers.AddressOrName("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") - unknown := pusers.AddressOrName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // valid but never used. + var ( + admin = pusers.AddressOrName("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") + alice = pusers.AddressOrName(testutils.TestAddress("alice")) + bob = pusers.AddressOrName(testutils.TestAddress("bob")) + ) type test struct { name string @@ -22,43 +26,44 @@ func TestReadOnlyPublicMethods(t *testing.T) { // check balances #1. { tests := []test{ - {"TotalSupply", 10100000000, func() uint64 { return TotalSupply() }}, - {"BalanceOf(admin)", 10000000000, func() uint64 { return BalanceOf(admin) }}, - {"BalanceOf(manfred)", 100000000, func() uint64 { return BalanceOf(manfred) }}, - {"Allowance(admin, manfred)", 0, func() uint64 { return Allowance(admin, manfred) }}, - {"BalanceOf(unknown)", 0, func() uint64 { return BalanceOf(unknown) }}, + {"TotalSupply", 10_000_000_000, func() uint64 { return TotalSupply() }}, + {"BalanceOf(admin)", 10_000_000_000, func() uint64 { return BalanceOf(admin) }}, + {"BalanceOf(alice)", 0, func() uint64 { return BalanceOf(alice) }}, + {"Allowance(admin, alice)", 0, func() uint64 { return Allowance(admin, alice) }}, + {"BalanceOf(bob)", 0, func() uint64 { return BalanceOf(bob) }}, } for _, tc := range tests { - if tc.fn() != tc.balance { - t.Errorf("%s: have: %d want: %d", tc.name, tc.fn(), tc.balance) - } + got := tc.fn() + uassert.Equal(t, got, tc.balance) } } - // unknown uses the faucet. - std.TestSetOrigCaller(users.Resolve(unknown)) + // bob uses the faucet. + std.TestSetOrigCaller(users.Resolve(bob)) Faucet() // check balances #2. { tests := []test{ - {"TotalSupply", 10110000000, func() uint64 { return TotalSupply() }}, - {"BalanceOf(admin)", 10000000000, func() uint64 { return BalanceOf(admin) }}, - {"BalanceOf(manfred)", 100000000, func() uint64 { return BalanceOf(manfred) }}, - {"Allowance(admin, manfred)", 0, func() uint64 { return Allowance(admin, manfred) }}, - {"BalanceOf(unknown)", 10000000, func() uint64 { return BalanceOf(unknown) }}, + {"TotalSupply", 10_010_000_000, func() uint64 { return TotalSupply() }}, + {"BalanceOf(admin)", 10_000_000_000, func() uint64 { return BalanceOf(admin) }}, + {"BalanceOf(alice)", 0, func() uint64 { return BalanceOf(alice) }}, + {"Allowance(admin, alice)", 0, func() uint64 { return Allowance(admin, alice) }}, + {"BalanceOf(bob)", 10_000_000, func() uint64 { return BalanceOf(bob) }}, } for _, tc := range tests { - if tc.fn() != tc.balance { - t.Errorf("%s: have: %d want: %d", tc.name, tc.fn(), tc.balance) - } + got := tc.fn() + uassert.Equal(t, got, tc.balance) } } } func TestErrConditions(t *testing.T) { - admin := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - empty := std.Address("") + var ( + admin = pusers.AddressOrName("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") + alice = pusers.AddressOrName(testutils.TestAddress("alice")) + empty = pusers.AddressOrName("") + ) type test struct { name string @@ -66,28 +71,16 @@ func TestErrConditions(t *testing.T) { fn func() } - std.TestSetOrigCaller(admin) + std.TestSetOrigCaller(users.Resolve(admin)) { tests := []test{ - {"Transfer(admin, 1)", "cannot send transfer to self", func() { Transfer(pusers.AddressOrName(admin), 1) }}, - {"Approve(empty, 1))", "invalid address", func() { Approve(pusers.AddressOrName(empty), 1) }}, + {"Transfer(admin, 1)", "cannot send transfer to self", func() { Transfer(admin, 1) }}, + {"Approve(empty, 1))", "invalid address", func() { Approve(empty, 1) }}, } for _, tc := range tests { - shouldPanicWithMsg(t, tc.fn, tc.msg) + t.Run(tc.name, func(t *testing.T) { + uassert.PanicsWithMessage(t, tc.msg, tc.fn) + }) } } } - -func shouldPanicWithMsg(t *testing.T, f func(), msg string) { - defer func() { - if r := recover(); r == nil { - t.Errorf("The code did not panic") - } else { - errMsg := error(r).Error() - if errMsg != msg { - t.Errorf("excepted panic(%v), got(%v)", msg, errMsg) - } - } - }() - f() -} diff --git a/examples/gno.land/r/demo/foo20/gno.mod b/examples/gno.land/r/demo/foo20/gno.mod index 96fefcf6163..4035f9b1200 100644 --- a/examples/gno.land/r/demo/foo20/gno.mod +++ b/examples/gno.land/r/demo/foo20/gno.mod @@ -2,6 +2,9 @@ module gno.land/r/demo/foo20 require ( gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/demo/users v0.0.0-latest gno.land/r/demo/users v0.0.0-latest diff --git a/examples/gno.land/r/demo/grc20factory/gno.mod b/examples/gno.land/r/demo/grc20factory/gno.mod new file mode 100644 index 00000000000..8d0fbd0c46b --- /dev/null +++ b/examples/gno.land/r/demo/grc20factory/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/demo/grc20factory + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory.gno b/examples/gno.land/r/demo/grc20factory/grc20factory.gno new file mode 100644 index 00000000000..f37a9370a9e --- /dev/null +++ b/examples/gno.land/r/demo/grc20factory/grc20factory.gno @@ -0,0 +1,136 @@ +package foo20 + +import ( + "std" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" +) + +var instances avl.Tree // symbol -> instance + +func New(name, symbol string, decimals uint, initialMint, faucet uint64) { + caller := std.PrevRealm().Addr() + NewWithAdmin(name, symbol, decimals, initialMint, faucet, caller) +} + +func NewWithAdmin(name, symbol string, decimals uint, initialMint, faucet uint64, admin std.Address) { + exists := instances.Has(symbol) + if exists { + panic("token already exists") + } + + banker := grc20.NewBanker(name, symbol, decimals) + if initialMint > 0 { + banker.Mint(admin, initialMint) + } + + inst := instance{ + banker: banker, + admin: ownable.NewWithAddress(admin), + faucet: faucet, + } + + instances.Set(symbol, &inst) +} + +type instance struct { + banker *grc20.Banker + admin *ownable.Ownable + faucet uint64 // per-request amount. disabled if 0. +} + +func (inst instance) Token() grc20.Token { return inst.banker.Token() } + +func TotalSupply(symbol string) uint64 { + inst := mustGetInstance(symbol) + return inst.Token().TotalSupply() +} + +func BalanceOf(symbol string, owner std.Address) uint64 { + inst := mustGetInstance(symbol) + return inst.Token().BalanceOf(owner) +} + +func Allowance(symbol string, owner, spender std.Address) uint64 { + inst := mustGetInstance(symbol) + return inst.Token().Allowance(owner, spender) +} + +func Transfer(symbol string, to std.Address, amount uint64) { + inst := mustGetInstance(symbol) + checkErr(inst.Token().Transfer(to, amount)) +} + +func Approve(symbol string, spender std.Address, amount uint64) { + inst := mustGetInstance(symbol) + checkErr(inst.Token().Approve(spender, amount)) +} + +func TransferFrom(symbol string, from, to std.Address, amount uint64) { + inst := mustGetInstance(symbol) + checkErr(inst.Token().TransferFrom(from, to, amount)) +} + +// faucet. +func Faucet(symbol string) { + inst := mustGetInstance(symbol) + if inst.faucet == 0 { + panic("faucet disabled for this token") + } + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.PrevRealm().Addr() + checkErr(inst.banker.Mint(caller, inst.faucet)) +} + +func Mint(symbol string, to std.Address, amount uint64) { + inst := mustGetInstance(symbol) + inst.admin.AssertCallerIsOwner() + checkErr(inst.banker.Mint(to, amount)) +} + +func Burn(symbol string, from std.Address, amount uint64) { + inst := mustGetInstance(symbol) + inst.admin.AssertCallerIsOwner() + checkErr(inst.banker.Burn(from, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return "TODO: list existing tokens and admins" + case c == 1: + symbol := parts[0] + inst := mustGetInstance(symbol) + return inst.banker.RenderHome() + case c == 3 && parts[1] == "balance": + symbol := parts[0] + inst := mustGetInstance(symbol) + owner := std.Address(parts[2]) + balance := inst.Token().BalanceOf(owner) + return ufmt.Sprintf("%d", balance) + default: + return "404\n" + } +} + +func mustGetInstance(symbol string) *instance { + t, exists := instances.Get(symbol) + if !exists { + panic("token instance does not exist") + } + return t.(*instance) +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno new file mode 100644 index 00000000000..5dfb6a760cc --- /dev/null +++ b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno @@ -0,0 +1,56 @@ +package foo20 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +func TestReadOnlyPublicMethods(t *testing.T) { + admin := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + manfred := std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") + unknown := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // valid but never used. + NewWithAdmin("Foo", "FOO", 4, 10_000*1_000_000, 0, admin) + NewWithAdmin("Bar", "BAR", 4, 10_000*1_000, 0, admin) + mustGetInstance("FOO").banker.Mint(manfred, 100_000_000) + + type test struct { + name string + balance uint64 + fn func() uint64 + } + + // check balances #1. + { + tests := []test{ + {"TotalSupply", 10_100_000_000, func() uint64 { return TotalSupply("FOO") }}, + {"BalanceOf(admin)", 10_000_000_000, func() uint64 { return BalanceOf("FOO", admin) }}, + {"BalanceOf(manfred)", 100_000_000, func() uint64 { return BalanceOf("FOO", manfred) }}, + {"Allowance(admin, manfred)", 0, func() uint64 { return Allowance("FOO", admin, manfred) }}, + {"BalanceOf(unknown)", 0, func() uint64 { return BalanceOf("FOO", unknown) }}, + } + for _, tc := range tests { + uassert.Equal(t, tc.balance, tc.fn(), "balance does not match") + } + } + return + + // unknown uses the faucet. + std.TestSetOrigCaller(unknown) + Faucet("FOO") + + // check balances #2. + { + tests := []test{ + {"TotalSupply", 10_110_000_000, func() uint64 { return TotalSupply("FOO") }}, + {"BalanceOf(admin)", 10_000_000_000, func() uint64 { return BalanceOf("FOO", admin) }}, + {"BalanceOf(manfred)", 100_000_000, func() uint64 { return BalanceOf("FOO", manfred) }}, + {"Allowance(admin, manfred)", 0, func() uint64 { return Allowance("FOO", admin, manfred) }}, + {"BalanceOf(unknown)", 10_000_000, func() uint64 { return BalanceOf("FOO", unknown) }}, + } + for _, tc := range tests { + uassert.Equal(t, tc.balance, tc.fn(), "balance does not match") + } + } +} diff --git a/examples/gno.land/r/demo/groups/misc.gno b/examples/gno.land/r/demo/groups/misc.gno index 2d578627632..24834b7b60c 100644 --- a/examples/gno.land/r/demo/groups/misc.gno +++ b/examples/gno.land/r/demo/groups/misc.gno @@ -78,9 +78,9 @@ func summaryOf(str string, length int) string { func displayAddressMD(addr std.Address) string { user := users.GetUserByAddress(addr) if user == nil { - return "[" + addr.String() + "](/r/users:" + addr.String() + ")" + return "[" + addr.String() + "](/r/demo/users:" + addr.String() + ")" } - return "[@" + user.Name + "](/r/users:" + user.Name + ")" + return "[@" + user.Name + "](/r/demo/users:" + user.Name + ")" } func usernameOf(addr std.Address) string { diff --git a/examples/gno.land/r/demo/groups/render.gno b/examples/gno.land/r/demo/groups/render.gno index ca5566ae28d..5af714b1ef5 100644 --- a/examples/gno.land/r/demo/groups/render.gno +++ b/examples/gno.land/r/demo/groups/render.gno @@ -27,7 +27,7 @@ func Render(path string) string { } parts := strings.Split(path, "/") if len(parts) == 1 { - // /r/Groups:Group_NAME + // /r/demo/groups:Group_NAME name := parts[0] groupI, exists := gGroupsByName.Get(name) if !exists { 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 41d515b9d0a..49ebb5219ec 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,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test import ( 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 index ca74c599130..6d328825dd6 100644 --- a/examples/gno.land/r/demo/groups/z_0_b_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_0_b_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test import ( @@ -16,4 +16,4 @@ func main() { } // Error: -// payment must not be less than 200000000 +// 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 10a4771c529..cf5902928db 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,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot 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 460fb79afbb..aeff9ab7774 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,11 +1,10 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot import ( "std" - "strconv" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" @@ -14,7 +13,7 @@ import ( var gid groups.GroupID -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main 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 3271e74e287..31a036d4e41 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,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot 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 93cd3b39d5f..6eaabb07cdf 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 @@ -1,15 +1,13 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot import ( "std" - "strconv" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" - "gno.land/r/demo/users" ) var gid groups.GroupID 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 f4a96905fc8..d1cc53d612f 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,11 +1,10 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot import ( "std" - "strconv" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" @@ -14,7 +13,7 @@ import ( var gid groups.GroupID -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main 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 bf10939c66f..fd8e485f16f 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 @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot 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 95f22f4ec4b..3caa726cbd3 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 @@ -1,11 +1,10 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot import ( "std" - "strconv" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" 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 270ddb8aa34..cbfff97c7a7 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,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot 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 4ec0ec211f2..4fddb768e08 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,4 +1,4 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot 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 8f98cfeb2dd..6230b110c74 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,11 +1,10 @@ -// PKGPATH: gno.land/r/groups_test +// PKGPATH: gno.land/r/demo/groups_test package groups_test // SEND: 200000000ugnot import ( "std" - "strconv" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" diff --git a/examples/gno.land/r/demo/keystore/gno.mod b/examples/gno.land/r/demo/keystore/gno.mod index af0b907c259..49b0f3494a4 100644 --- a/examples/gno.land/r/demo/keystore/gno.mod +++ b/examples/gno.land/r/demo/keystore/gno.mod @@ -3,5 +3,6 @@ module gno.land/r/demo/keystore require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index 998c8457751..ffd8e60936f 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -1,12 +1,13 @@ package keystore import ( - "fmt" "std" "strings" "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" ) func TestRender(t *testing.T) { @@ -70,9 +71,8 @@ func TestRender(t *testing.T) { } else { act = strings.TrimSpace(Render(p)) } - if act != tc.exp { - t.Errorf("%v -> '%s', got '%s', wanted '%s'", tc.ps, p, act, tc.exp) - } + + uassert.Equal(t, tc.exp, act, ufmt.Sprintf("%v -> '%s'", tc.ps, p)) }) } } diff --git a/examples/gno.land/r/demo/microblog/gno.mod b/examples/gno.land/r/demo/microblog/gno.mod index 3285127b025..26349e481d4 100644 --- a/examples/gno.land/r/demo/microblog/gno.mod +++ b/examples/gno.land/r/demo/microblog/gno.mod @@ -1,9 +1,9 @@ module gno.land/r/demo/microblog require ( - gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/microblog v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/microblog/microblog_test.gno b/examples/gno.land/r/demo/microblog/microblog_test.gno index 83b20286fee..a3c8f04ee7f 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -1,14 +1,12 @@ package microblog import ( - "log" "std" "strings" "testing" - "gno.land/p/demo/avl" - "gno.land/p/demo/microblog" "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" ) func TestMicroblog(t *testing.T) { @@ -19,46 +17,34 @@ func TestMicroblog(t *testing.T) { std.TestSetOrigCaller(author1) - if Render("/wrongpath") != "404" { - t.Fatalf("rendering not giving 404") - } - if Render("") == "404" { - t.Fatalf("rendering / should not give 404") - } - if err := m.NewPost("goodbyte, web2"); err != nil { - t.Fatalf("could not create post") - } - if _, err := m.GetPage(author1.String()); err != nil { - t.Fatalf("silo should exist") - } - if _, err := m.GetPage("no such author"); err == nil { - t.Fatalf("silo should not exist") - } + urequire.Equal(t, "404", Render("/wrongpath"), "rendering not giving 404") + urequire.NotEqual(t, "404", Render(""), "rendering / should not give 404") + urequire.NoError(t, m.NewPost("goodbyte, web2"), "could not create post") + + _, err := m.GetPage(author1.String()) + urequire.NoError(t, err, "silo should exist") + + _, err = m.GetPage("no such author") + urequire.Error(t, err, "silo should not exist") std.TestSetOrigCaller(author2) - if err := m.NewPost("hello, web3"); err != nil { - t.Fatalf("could not create post") - } - if err := m.NewPost("hello again, web3"); err != nil { - t.Fatalf("could not create post") - } - if err := m.NewPost("hi again,\n web4?"); err != nil { - t.Fatalf("could not create post") - } + urequire.NoError(t, m.NewPost("hello, web3"), "could not create post") + urequire.NoError(t, m.NewPost("hello again, web3"), "could not create post") + urequire.NoError(t, m.NewPost("hi again,\n web4?"), "could not create post") println("--- MICROBLOG ---\n\n") - if rendering := Render(""); rendering != `# gno-based microblog + + expected := `# gno-based microblog # pages - [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) - [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) -` { - t.Fatalf("incorrect rendering /: '%s'", rendering) - } +` + urequire.Equal(t, expected, Render(""), "incorrect rendering") - if rendering := strings.TrimSpace(Render(author1.String())); rendering != `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) + expected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) joined 2009-13-02, last updated 2009-13-02 @@ -66,11 +52,11 @@ joined 2009-13-02, last updated 2009-13-02 > goodbyte, web2 > -> *Fri, 13 Feb 2009 23:31:30 UTC*` { - t.Fatalf("incorrect rendering /: '%s'", rendering) - } +> *Fri, 13 Feb 2009 23:31:30 UTC*` - if rendering := strings.TrimSpace(Render(author2.String())); rendering != `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) + urequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), "incorrect rendering") + + expected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) joined 2009-13-02, last updated 2009-13-02 @@ -88,7 +74,7 @@ joined 2009-13-02, last updated 2009-13-02 > hello, web3 > -> *Fri, 13 Feb 2009 23:31:30 UTC*` { - t.Fatalf("incorrect rendering /: '%s'", rendering) - } +> *Fri, 13 Feb 2009 23:31:30 UTC*` + + urequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), "incorrect rendering") } diff --git a/examples/gno.land/r/demo/nft/README.md b/examples/gno.land/r/demo/nft/README.md index 2b4bc827e88..6f3b12bddf4 100644 --- a/examples/gno.land/r/demo/nft/README.md +++ b/examples/gno.land/r/demo/nft/README.md @@ -2,10 +2,10 @@ NFT's are all the rage these days, for various reasons. I 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. - - [EIP-721](https://eips.ethereum.org/EIPS/eip-721) - - [gno.land/r/nft/nft.go](https://gno.land/r/nft/nft.go) - - [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno) +- [EIP-721](https://eips.ethereum.org/EIPS/eip-721) +- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go) +- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno) -In short, this demonstrates how to implement Ethereum contract interfaces in Gno.land; by using only standard Go language features. +In short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features. -Please leave a comment ([guide](https://gno.land/r/boards:gnolang/1)). +Please leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)). diff --git a/examples/gno.land/r/demo/nft/z_0_filetest.gno b/examples/gno.land/r/demo/nft/z_0_filetest.gno index 973d5f95eab..c72c9191b56 100644 --- a/examples/gno.land/r/demo/nft/z_0_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_0_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/nft_test +// PKGPATH: gno.land/r/demo/nft_test package nft_test import ( @@ -22,7 +22,7 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/nft"] // switchrealm["gno.land/r/demo/nft"] -// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={ +// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={ // "Fields": [ // { // "T": { @@ -62,13 +62,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:11", +// "ModTime": "0", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:10", +// "RefCount": "1" +// } +// } +// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={ +// "ObjectInfo": { +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:10", // "ModTime": "0", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:7", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:9", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/nft.NFToken" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "564a9e78be869bd258fc3c9ad56f5a75ed68818f", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:11" +// } // } // } -// c[67c479d3d51d4056b2f4111d5352912a00be311e:7]={ +// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={ // "Fields": [ // { // "T": { @@ -90,19 +109,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b53ffc464e1b5655d19b9d5277f3491717c24aca", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:10" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/nft.NFToken" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c06f58d0ff2bc26ad3e65e953b127a0d03353e97", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:8" -// } -// } +// "TV": null // } // }, // { @@ -138,10 +151,29 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:7", +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:9", +// "ModTime": "0", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", +// "RefCount": "1" +// } +// } +// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={ +// "ObjectInfo": { +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", // "ModTime": "0", // "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b1d928b3716b147c92730e8d234162bec2f0f2fc", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:9" +// } // } // } // u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={ @@ -156,25 +188,19 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b229b824842ec3e7f2341e33d0fa0ca77af2f480", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "45a64533aa57b49b6b4a1d3f6de79db8bea3a710", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:7" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { // "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", -// "ModTime": "6", +// "ModTime": "7", // "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", // "RefCount": "1" // } @@ -196,7 +222,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "dad3106a54e1facb92bce473898b8aec0eb930ff", +// "Hash": "1e0b9dddb406b4f50500a022266a4cb8a4ea38c6", // "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:5" // } // }, @@ -214,10 +240,10 @@ func main() { // ], // "ObjectInfo": { // "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", -// "ModTime": "6", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:2", +// "ModTime": "7", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:3", // "RefCount": "1" // } // } // switchrealm["gno.land/r/demo/nft"] -// switchrealm["gno.land/r/nft_test"] +// switchrealm["gno.land/r/demo/nft_test"] diff --git a/examples/gno.land/r/demo/nft/z_1_filetest.gno b/examples/gno.land/r/demo/nft/z_1_filetest.gno index 15b0a9a7785..990120d127a 100644 --- a/examples/gno.land/r/demo/nft/z_1_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_1_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/nft_test +// PKGPATH: gno.land/r/demo/nft_test package nft_test import ( 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 00d6eb0c0f0..91c48bd5957 100644 --- a/examples/gno.land/r/demo/nft/z_2_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_2_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/nft_test +// PKGPATH: gno.land/r/demo/nft_test package nft_test import ( 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 00a4d00291b..d0210c8ba4d 100644 --- a/examples/gno.land/r/demo/nft/z_3_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_3_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/nft_test +// PKGPATH: gno.land/r/demo/nft_test package nft_test import ( 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 773444ff8fa..b38ce8ea190 100644 --- a/examples/gno.land/r/demo/nft/z_4_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_4_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/nft_test +// PKGPATH: gno.land/r/demo/nft_test package nft_test import ( diff --git a/examples/gno.land/r/demo/profile/gno.mod b/examples/gno.land/r/demo/profile/gno.mod new file mode 100644 index 00000000000..e7feac5d680 --- /dev/null +++ b/examples/gno.land/r/demo/profile/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/demo/profile + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/mux v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno new file mode 100644 index 00000000000..cc7d80e016d --- /dev/null +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -0,0 +1,121 @@ +package profile + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/mux" +) + +var ( + fields = avl.NewTree() + router = mux.NewRouter() +) + +const ( + DisplayName = "DisplayName" + Homepage = "Homepage" + Bio = "Bio" + Age = "Age" + Location = "Location" + Avatar = "Avatar" + GravatarEmail = "GravatarEmail" + AvailableForHiring = "AvailableForHiring" + InvalidField = "InvalidField" +) + +func init() { + router.HandleFunc("", homeHandler) + router.HandleFunc("u/{addr}", profileHandler) + router.HandleFunc("f/{addr}/{field}", fieldHandler) +} + +// list of supported string fields +var stringFields = map[string]bool{ + DisplayName: true, + Homepage: true, + Bio: true, + Location: true, + Avatar: true, + GravatarEmail: true, +} + +// list of support int fields +var intFields = map[string]bool{ + Age: true, +} + +// list of support bool fields +var boolFields = map[string]bool{ + AvailableForHiring: true, +} + +// Setters + +func SetStringField(field, value string) error { + addr := std.PrevRealm().Addr() + if _, ok := stringFields[field]; !ok { + return errors.New("invalid string field") + } + + key := addr.String() + ":" + field + fields.Set(key, value) + + return nil +} + +func SetIntField(field string, value int) error { + addr := std.PrevRealm().Addr() + + if _, ok := intFields[field]; !ok { + return errors.New("invalid int field") + } + + key := addr.String() + ":" + field + fields.Set(key, value) + + return nil +} + +func SetBoolField(field string, value bool) error { + addr := std.PrevRealm().Addr() + + if _, ok := boolFields[field]; !ok { + return errors.New("invalid bool field") + } + + key := addr.String() + ":" + field + fields.Set(key, value) + + return nil +} + +// Getters + +func GetStringField(addr std.Address, field, def string) string { + key := addr.String() + ":" + field + if value, ok := fields.Get(key); ok { + return value.(string) + } + + return def +} + +func GetBoolField(addr std.Address, field string, def bool) bool { + key := addr.String() + ":" + field + if value, ok := fields.Get(key); ok { + return value.(bool) + } + + return def +} + +func GetIntField(addr std.Address, field string, def int) int { + key := addr.String() + ":" + field + if value, ok := fields.Get(key); ok { + return value.(int) + } + + return def +} diff --git a/examples/gno.land/r/demo/profile/profile_test.gno b/examples/gno.land/r/demo/profile/profile_test.gno new file mode 100644 index 00000000000..987632a594d --- /dev/null +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -0,0 +1,118 @@ +package profile + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +// Global addresses for test users +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") + charlie = testutils.TestAddress("charlie") + dave = testutils.TestAddress("dave") + eve = testutils.TestAddress("eve") + frank = testutils.TestAddress("frank") + user1 = testutils.TestAddress("user1") + user2 = testutils.TestAddress("user2") +) + +func TestStringFields(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + // Get before setting + name := GetStringField(alice, DisplayName, "anon") + uassert.Equal(t, "anon", name) + + // Set + err := SetStringField(DisplayName, "Alice foo") + uassert.NoError(t, err) + err = SetStringField(Homepage, "https://example.com") + uassert.NoError(t, err) + + // Get after setting + name = GetStringField(alice, DisplayName, "anon") + homepage := GetStringField(alice, Homepage, "") + bio := GetStringField(alice, Bio, "42") + + uassert.Equal(t, "Alice foo", name) + uassert.Equal(t, "https://example.com", homepage) + uassert.Equal(t, "42", bio) +} + +func TestIntFields(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(bob)) + + // Get before setting + age := GetIntField(bob, Age, 25) + uassert.Equal(t, 25, age) + + // Set + err := SetIntField(Age, 30) + uassert.NoError(t, err) + + // Get after setting + age = GetIntField(bob, Age, 25) + uassert.Equal(t, 30, age) +} + +func TestBoolFields(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(charlie)) + + // Get before setting + hiring := GetBoolField(charlie, AvailableForHiring, false) + uassert.Equal(t, false, hiring) + + // Set + err := SetBoolField(AvailableForHiring, true) + uassert.NoError(t, err) + + // Get after setting + hiring = GetBoolField(charlie, AvailableForHiring, false) + uassert.Equal(t, true, hiring) +} + +func TestInvalidStringField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(dave)) + + err := SetStringField(InvalidField, "test") + uassert.Error(t, err) +} + +func TestInvalidIntField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(eve)) + + err := SetIntField(InvalidField, 123) + uassert.Error(t, err) +} + +func TestInvalidBoolField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(frank)) + + err := SetBoolField(InvalidField, true) + uassert.Error(t, err) +} + +func TestMultipleProfiles(t *testing.T) { + // Set profile for user1 + std.TestSetRealm(std.NewUserRealm(user1)) + err := SetStringField(DisplayName, "User One") + uassert.NoError(t, err) + + // Set profile for user2 + std.TestSetRealm(std.NewUserRealm(user2)) + err = SetStringField(DisplayName, "User Two") + uassert.NoError(t, err) + + // Get profiles + std.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1 + name1 := GetStringField(user1, DisplayName, "anon") + std.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2 + name2 := GetStringField(user2, DisplayName, "anon") + + uassert.Equal(t, "User One", name1) + uassert.Equal(t, "User Two", name2) +} diff --git a/examples/gno.land/r/demo/profile/render.gno b/examples/gno.land/r/demo/profile/render.gno new file mode 100644 index 00000000000..4ff295e65eb --- /dev/null +++ b/examples/gno.land/r/demo/profile/render.gno @@ -0,0 +1,102 @@ +package profile + +import ( + "bytes" + "std" + + "gno.land/p/demo/mux" + "gno.land/p/demo/ufmt" +) + +const ( + BaseURL = "/r/demo/profile" + SetStringFieldURL = BaseURL + "?help&__func=SetStringField&field=%s" + SetIntFieldURL = BaseURL + "?help&__func=SetIntField&field=%s" + SetBoolFieldURL = BaseURL + "?help&__func=SetBoolField&field=%s" + ViewAllFieldsURL = BaseURL + ":u/%s" + ViewFieldURL = BaseURL + ":f/%s/%s" +) + +func homeHandler(res *mux.ResponseWriter, req *mux.Request) { + var b bytes.Buffer + + b.WriteString("## Setters\n") + for field := range stringFields { + link := ufmt.Sprintf(SetStringFieldURL, field) + b.WriteString(ufmt.Sprintf("- [Set %s](%s)\n", field, link)) + } + + for field := range intFields { + link := ufmt.Sprintf(SetIntFieldURL, field) + b.WriteString(ufmt.Sprintf("- [Set %s](%s)\n", field, link)) + } + + for field := range boolFields { + link := ufmt.Sprintf(SetBoolFieldURL, field) + b.WriteString(ufmt.Sprintf("- [Set %s Field](%s)\n", field, link)) + } + + b.WriteString("\n---\n\n") + + res.Write(b.String()) +} + +func profileHandler(res *mux.ResponseWriter, req *mux.Request) { + var b bytes.Buffer + addr := req.GetVar("addr") + + b.WriteString(ufmt.Sprintf("# Profile %s\n", addr)) + + address := std.Address(addr) + + for field := range stringFields { + value := GetStringField(address, field, "n/a") + link := ufmt.Sprintf(SetStringFieldURL, field) + b.WriteString(ufmt.Sprintf("- %s: %s [Edit](%s)\n", field, value, link)) + } + + for field := range intFields { + value := GetIntField(address, field, 0) + link := ufmt.Sprintf(SetIntFieldURL, field) + b.WriteString(ufmt.Sprintf("- %s: %d [Edit](%s)\n", field, value, link)) + } + + for field := range boolFields { + value := GetBoolField(address, field, false) + link := ufmt.Sprintf(SetBoolFieldURL, field) + b.WriteString(ufmt.Sprintf("- %s: %t [Edit](%s)\n", field, value, link)) + } + + res.Write(b.String()) +} + +func fieldHandler(res *mux.ResponseWriter, req *mux.Request) { + var b bytes.Buffer + addr := req.GetVar("addr") + field := req.GetVar("field") + + b.WriteString(ufmt.Sprintf("# Field %s for %s\n", field, addr)) + + address := std.Address(addr) + value := "n/a" + var editLink string + + if _, ok := stringFields[field]; ok { + value = ufmt.Sprintf("%s", GetStringField(address, field, "n/a")) + editLink = ufmt.Sprintf(SetStringFieldURL+"&addr=%s&value=%s", field, addr, value) + } else if _, ok := intFields[field]; ok { + value = ufmt.Sprintf("%d", GetIntField(address, field, 0)) + editLink = ufmt.Sprintf(SetIntFieldURL+"&addr=%s&value=%s", field, addr, value) + } else if _, ok := boolFields[field]; ok { + value = ufmt.Sprintf("%t", GetBoolField(address, field, false)) + editLink = ufmt.Sprintf(SetBoolFieldURL+"&addr=%s&value=%s", field, addr, value) + } + + b.WriteString(ufmt.Sprintf("- %s: %s [Edit](%s)\n", field, value, editLink)) + + res.Write(b.String()) +} + +func Render(path string) string { + return router.Render(path) +} diff --git a/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno index 373d737b3b5..e494ec5cbc8 100644 --- a/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno +++ b/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno @@ -1,9 +1,6 @@ package main import ( - "std" - "time" - "gno.land/r/demo/tamagotchi" ) diff --git a/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno b/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno new file mode 100644 index 00000000000..97273f642de --- /dev/null +++ b/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno @@ -0,0 +1,29 @@ +package crossrealm + +import ( + "gno.land/p/demo/tests/p_crossrealm" + "gno.land/p/demo/ufmt" +) + +type LocalStruct struct { + A int +} + +func (ls *LocalStruct) String() string { + return ufmt.Sprintf("LocalStruct{%d}", ls.A) +} + +// local is saved locally in this realm +var local *LocalStruct + +func init() { + local = &LocalStruct{A: 123} +} + +// Make1 returns a local object wrapped by a p struct +func Make1() *p_crossrealm.Container { + return &p_crossrealm.Container{ + A: 1, + B: local, + } +} diff --git a/examples/gno.land/r/demo/tests/crossrealm/gno.mod b/examples/gno.land/r/demo/tests/crossrealm/gno.mod new file mode 100644 index 00000000000..71a89ec2ec5 --- /dev/null +++ b/examples/gno.land/r/demo/tests/crossrealm/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/tests/crossrealm + +require ( + gno.land/p/demo/tests/p_crossrealm v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/tests/gno.mod b/examples/gno.land/r/demo/tests/gno.mod index 9c5162f848e..c51571e7d04 100644 --- a/examples/gno.land/r/demo/tests/gno.mod +++ b/examples/gno.land/r/demo/tests/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/tests require ( - gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/nestedpkg v0.0.0-latest gno.land/r/demo/tests/subtests v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/tests/nestedpkg_test.gno b/examples/gno.land/r/demo/tests/nestedpkg_test.gno new file mode 100644 index 00000000000..904e8cc71a7 --- /dev/null +++ b/examples/gno.land/r/demo/tests/nestedpkg_test.gno @@ -0,0 +1,73 @@ +package tests + +import ( + "std" + "testing" +) + +func TestNestedPkg(t *testing.T) { + // direct child + cur := "gno.land/r/demo/tests/foo" + std.TestSetRealm(std.NewCodeRealm(cur)) + if !IsCallerSubPath() { + t.Errorf(cur + " should be a sub path") + } + if IsCallerParentPath() { + t.Errorf(cur + " should not be a parent path") + } + if !HasCallerSameNamespace() { + t.Errorf(cur + " should be from the same namespace") + } + + // grand-grand-child + cur = "gno.land/r/demo/tests/foo/bar/baz" + std.TestSetRealm(std.NewCodeRealm(cur)) + if !IsCallerSubPath() { + t.Errorf(cur + " should be a sub path") + } + if IsCallerParentPath() { + t.Errorf(cur + " should not be a parent path") + } + if !HasCallerSameNamespace() { + t.Errorf(cur + " should be from the same namespace") + } + + // direct parent + cur = "gno.land/r/demo" + std.TestSetRealm(std.NewCodeRealm(cur)) + if IsCallerSubPath() { + t.Errorf(cur + " should not be a sub path") + } + if !IsCallerParentPath() { + t.Errorf(cur + " should be a parent path") + } + if !HasCallerSameNamespace() { + t.Errorf(cur + " should be from the same namespace") + } + + // fake parent (prefix) + cur = "gno.land/r/dem" + std.TestSetRealm(std.NewCodeRealm(cur)) + if IsCallerSubPath() { + t.Errorf(cur + " should not be a sub path") + } + if IsCallerParentPath() { + t.Errorf(cur + " should not be a parent path") + } + if HasCallerSameNamespace() { + t.Errorf(cur + " should not be from the same namespace") + } + + // different namespace + cur = "gno.land/r/foo" + std.TestSetRealm(std.NewCodeRealm(cur)) + if IsCallerSubPath() { + t.Errorf(cur + " should not be a sub path") + } + if IsCallerParentPath() { + t.Errorf(cur + " should not be a parent path") + } + if HasCallerSameNamespace() { + t.Errorf(cur + " should not be from the same namespace") + } +} diff --git a/examples/gno.land/r/demo/tests/realm_compositelit.gno b/examples/gno.land/r/demo/tests/realm_compositelit.gno new file mode 100644 index 00000000000..57c42280c41 --- /dev/null +++ b/examples/gno.land/r/demo/tests/realm_compositelit.gno @@ -0,0 +1,22 @@ +package tests + +type ( + Word uint + nat []Word +) + +var zero = &Int{ + neg: true, + abs: []Word{0}, +} + +// structLit +type Int struct { + neg bool + abs nat +} + +func GetZeroType() nat { + a := zero.abs + return a +} diff --git a/examples/gno.land/r/demo/tests/realm_method38d.gno b/examples/gno.land/r/demo/tests/realm_method38d.gno new file mode 100644 index 00000000000..b1dbab67e1f --- /dev/null +++ b/examples/gno.land/r/demo/tests/realm_method38d.gno @@ -0,0 +1,19 @@ +package tests + +var abs nat + +func (n nat) Add() nat { + return []Word{0} +} + +func GetAbs() nat { + abs = []Word{0} + + return abs +} + +func AbsAdd() nat { + rt := GetAbs().Add() + + return rt +} diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno index e8796a73081..6bf43cba5eb 100644 --- a/examples/gno.land/r/demo/tests/subtests/subtests.gno +++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno @@ -15,3 +15,11 @@ func GetPrevRealm() std.Realm { func Exec(fn func()) { fn() } + +func CallAssertOriginCall() { + std.AssertOriginCall() +} + +func CallIsOriginCall() bool { + return std.IsOriginCall() +} diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index 2062df6903d..421ac6528c9 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -3,6 +3,7 @@ package tests import ( "std" + "gno.land/p/demo/nestedpkg" rsubtests "gno.land/r/demo/tests/subtests" ) @@ -17,7 +18,7 @@ func Counter() int { } func CurrentRealmPath() string { - return std.CurrentRealmPath() + return std.CurrentRealm().PkgPath() } var initOrigCaller = std.GetOrigCaller() @@ -26,14 +27,22 @@ func InitOrigCaller() std.Address { return initOrigCaller } -func AssertOriginCall() { +func CallAssertOriginCall() { std.AssertOriginCall() } -func IsOriginCall() bool { +func CallIsOriginCall() bool { return std.IsOriginCall() } +func CallSubtestsAssertOriginCall() { + rsubtests.CallAssertOriginCall() +} + +func CallSubtestsIsOriginCall() bool { + return rsubtests.CallIsOriginCall() +} + //---------------------------------------- // Test structure to ensure cross-realm modification is prevented. @@ -91,3 +100,15 @@ func GetRSubtestsPrevRealm() std.Realm { func Exec(fn func()) { fn() } + +func IsCallerSubPath() bool { + return nestedpkg.IsCallerSubPath() +} + +func IsCallerParentPath() bool { + return nestedpkg.IsCallerParentPath() +} + +func HasCallerSameNamespace() bool { + return nestedpkg.IsSameNamespace() +} diff --git a/examples/gno.land/r/demo/tests/tests_test.gno b/examples/gno.land/r/demo/tests/tests_test.gno index 3dcbeecf18c..ccbc6b91265 100644 --- a/examples/gno.land/r/demo/tests/tests_test.gno +++ b/examples/gno.land/r/demo/tests/tests_test.gno @@ -3,33 +3,43 @@ package tests import ( "std" "testing" - - "gno.land/p/demo/testutils" ) func TestAssertOriginCall(t *testing.T) { - // No-panic case - AssertOriginCall() - if !IsOriginCall() { + // CallAssertOriginCall(): no panic + CallAssertOriginCall() + if !CallIsOriginCall() { t.Errorf("expected IsOriginCall=true but got false") } - // Panic case + // CallAssertOriginCall() from a block: panic expectedReason := "invalid non-origin call" + func() { + defer func() { + r := recover() + if r == nil || r.(string) != expectedReason { + t.Errorf("expected panic with '%v', got '%v'", expectedReason, r) + } + }() + // 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() { + t.Errorf("expected IsOriginCall=false but got true") + } + CallAssertOriginCall() + }() + + // CallSubtestsAssertOriginCall(): panic defer func() { r := recover() if r == nil || r.(string) != expectedReason { t.Errorf("expected panic with '%v', got '%v'", expectedReason, r) } }() - func() { - // if called inside a function literal, this is no longer an origin call - // because there's one additional frame (the function literal). - if IsOriginCall() { - t.Errorf("expected IsOriginCall=false but got true") - } - AssertOriginCall() - }() + if CallSubtestsIsOriginCall() { + t.Errorf("expected IsOriginCall=false but got true") + } + CallSubtestsAssertOriginCall() } func TestPrevRealm(t *testing.T) { diff --git a/examples/gno.land/r/demo/tests/z0_filetest.gno b/examples/gno.land/r/demo/tests/z0_filetest.gno index c4beb8e2005..ff625d4906f 100644 --- a/examples/gno.land/r/demo/tests/z0_filetest.gno +++ b/examples/gno.land/r/demo/tests/z0_filetest.gno @@ -5,24 +5,24 @@ import ( ) func main() { - println("IsOriginCall:", tests.IsOriginCall()) - tests.AssertOriginCall() - println("AssertOriginCall doesn't panic when called directly") + println("tests.CallIsOriginCall:", tests.CallIsOriginCall()) + tests.CallAssertOriginCall() + println("tests.CallAssertOriginCall doesn't panic when called directly") - func() { - // if called inside a function literal, this is no longer an origin call - // because there's one additional frame (the function literal). - println("IsOriginCall:", tests.IsOriginCall()) + { + // if called inside a block, this is no longer an origin call because + // there's one additional frame (the block). + println("tests.CallIsOriginCall:", tests.CallIsOriginCall()) defer func() { r := recover() - println("AssertOriginCall panics if when called inside a function literal:", r) + println("tests.AssertOriginCall panics if when called inside a function literal:", r) }() - tests.AssertOriginCall() - }() + tests.CallAssertOriginCall() + } } // Output: -// IsOriginCall: true -// AssertOriginCall doesn't panic when called directly -// IsOriginCall: false -// AssertOriginCall panics if when called inside a function literal: invalid non-origin call +// tests.CallIsOriginCall: true +// tests.CallAssertOriginCall doesn't panic when called directly +// tests.CallIsOriginCall: true +// tests.AssertOriginCall panics if when called inside a function literal: undefined diff --git a/examples/gno.land/r/demo/todolist/gno.mod b/examples/gno.land/r/demo/todolist/gno.mod index 563bab74ad5..36909859a6f 100644 --- a/examples/gno.land/r/demo/todolist/gno.mod +++ b/examples/gno.land/r/demo/todolist/gno.mod @@ -4,5 +4,6 @@ require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/seqid v0.0.0-latest gno.land/p/demo/todolist v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/todolist/todolist_test.gno b/examples/gno.land/r/demo/todolist/todolist_test.gno index db55110851e..6446732df3e 100644 --- a/examples/gno.land/r/demo/todolist/todolist_test.gno +++ b/examples/gno.land/r/demo/todolist/todolist_test.gno @@ -6,6 +6,7 @@ import ( "testing" "gno.land/p/demo/todolist" + "gno.land/p/demo/uassert" ) var ( @@ -16,71 +17,44 @@ var ( func TestNewTodoList(t *testing.T) { title := "My Todo List" tlid, _ := NewTodoList(title) - if tlid != 1 { - t.Errorf("Expected tlid to be 1, but got %d", tlid) - } + uassert.Equal(t, 1, tlid, "tlid does not match") // get the todolist node from the tree node, _ = todolistTree.Get(strconv.Itoa(tlid)) // convert the node to a TodoList struct tdl = node.(*todolist.TodoList) - if tdl.Title != title { - t.Errorf("Expected title to be %s, but got %s", title, tdl.Title) - } - if tdl.Owner != std.GetOrigCaller() { - t.Errorf("Expected owner to be %s, but got %s", std.GetOrigCaller(), tdl.Owner) - } - if len(tdl.GetTasks()) != 0 { - t.Errorf("Expected no tasks in the todo list, but got %d tasks", len(tdl.GetTasks())) - } + 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, 0, len(tdl.GetTasks()), "Expected no tasks in the todo list") } func TestAddTask(t *testing.T) { AddTask(1, "Task 1") tasks := tdl.GetTasks() - if len(tasks) != 1 { - t.Errorf("Expected 1 task in the todo list, but got %d tasks", len(tasks)) - } - - if tasks[0].Title != "Task 1" { - t.Errorf("Expected task title to be 'Task 1', but got '%s'", tasks[0].Title) - } - - if tasks[0].Done { - t.Errorf("Expected task to be not done, but it is marked as done") - } + uassert.Equal(t, 1, len(tasks), "total task does not match") + uassert.Equal(t, "Task 1", tasks[0].Title, "task title does not match") + uassert.False(t, tasks[0].Done, "Expected task to be not done") } func TestToggleTaskStatus(t *testing.T) { ToggleTaskStatus(1, 0) task := tdl.GetTasks()[0] - - if !task.Done { - t.Errorf("Expected task to be done, but it is not marked as done") - } + uassert.True(t, task.Done, "Expected task to be done, but it is not marked as done") ToggleTaskStatus(1, 0) - - if task.Done { - t.Errorf("Expected task to be not done, but it is marked as done") - } + uassert.False(t, task.Done, "Expected task to be not done, but it is marked as done") } func TestRemoveTask(t *testing.T) { RemoveTask(1, 0) tasks := tdl.GetTasks() - - if len(tasks) != 0 { - t.Errorf("Expected no tasks in the todo list, but got %d tasks", len(tasks)) - } + uassert.Equal(t, 0, len(tasks), "Expected no tasks in the todo list") } func TestRemoveTodoList(t *testing.T) { RemoveTodoList(1) - - if todolistTree.Size() != 0 { - t.Errorf("Expected no tasks in the todo list, but got %d tasks", todolistTree.Size()) - } + uassert.Equal(t, 0, todolistTree.Size(), "Expected no tasks in the todo list") } diff --git a/examples/gno.land/r/demo/ui/gno.mod b/examples/gno.land/r/demo/ui/gno.mod index 42be8cec3f0..0ef5d9dd40e 100644 --- a/examples/gno.land/r/demo/ui/gno.mod +++ b/examples/gno.land/r/demo/ui/gno.mod @@ -1,3 +1,6 @@ module gno.land/r/demo/ui -require gno.land/p/demo/ui v0.0.0-latest +require ( + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ui v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/ui/ui_test.gno b/examples/gno.land/r/demo/ui/ui_test.gno index 65db8c9af14..7cb40d3f96d 100644 --- a/examples/gno.land/r/demo/ui/ui_test.gno +++ b/examples/gno.land/r/demo/ui/ui_test.gno @@ -1,12 +1,13 @@ package ui -import "testing" +import ( + "testing" + + "gno.land/p/demo/uassert" +) func TestRender(t *testing.T) { got := Render("") expected := "# UI Demo\n\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\n\n\nSimple UI demonstration.\n\n- a text\n- [a relative link](r/demo/ui:foobar)\n- another text\n- **a bold text**\n- _italic text_\n- raw markdown with **bold** text in the middle.\n- `some inline code`\n- [a remote link](https://gno.land)\n\nanother string.\n\na paragraph.\n\n\n---\n\n\nI'm the footer.\n\n" - - if got != expected { - t.Errorf("-\nexpected: %q\ngot: %q.", expected, got) - } + uassert.Equal(t, expected, got) } diff --git a/examples/gno.land/r/demo/userbook/userbook.gno b/examples/gno.land/r/demo/userbook/userbook.gno index 85b76cbf28f..c49bd90fa42 100644 --- a/examples/gno.land/r/demo/userbook/userbook.gno +++ b/examples/gno.land/r/demo/userbook/userbook.gno @@ -28,6 +28,7 @@ const ( defaultPageSize = 20 pathArgument = "number" subPath = "page/{" + pathArgument + "}" + signUpEvent = "SignUp" ) func init() { @@ -48,10 +49,12 @@ func SignUp() string { caller := std.PrevRealm().Addr().String() height := std.GetHeight() + // Check if the user is already signed up if _, exists := tracker.Get(caller); exists { panic(caller + " is already signed up!") } + // Sign up the user tracker.Set(caller, struct{}{}) signup := Signup{ caller, @@ -59,6 +62,8 @@ func SignUp() string { } signups = append(signups, signup) + std.Emit(signUpEvent, "SignedUpAccount", signup.account) + return ufmt.Sprintf("%s added to userbook up at block #%d!", signup.account, signup.height) } diff --git a/examples/gno.land/r/demo/users/preregister.gno b/examples/gno.land/r/demo/users/preregister.gno new file mode 100644 index 00000000000..a6377c54938 --- /dev/null +++ b/examples/gno.land/r/demo/users/preregister.gno @@ -0,0 +1,66 @@ +package users + +import ( + "std" + + "gno.land/p/demo/users" +) + +// 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 +} + +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) + } +} diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 4fe486a71d0..9b8e93b579b 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -14,13 +14,15 @@ import ( // State var ( - admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" - name2User avl.Tree // Name -> *users.User - addr2User avl.Tree // std.Address -> *users.User - invites avl.Tree // string(inviter+":"+invited) -> true - counter int // user id counter - minFee int64 = 200 * 1000000 // minimum gnot must be paid to register. - maxFeeMult int64 = 10 // maximum multiples of minFee accepted. + admin std.Address = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" // @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. ) //---------------------------------------- @@ -34,8 +36,10 @@ func Register(inviter std.Address, name string, profile string) { if caller != std.GetOrigCaller() { panic("should not happen") // because std.AssertOrigCall(). } + sentCoins := std.GetOrigSend() - minCoin := std.Coin{"ugnot", minFee} + minCoin := std.NewCoin("ugnot", minFee) + if inviter == "" { // banker := std.GetBanker(std.BankerTypeOrigSend) if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { @@ -55,19 +59,35 @@ func Register(inviter std.Address, name string, profile string) { } invites.Remove(invitekey) } + // assert not already registered. _, ok := name2User.Get(name) if ok { - panic("name already registered") + panic("name already registered: " + name) } _, ok = addr2User.Get(caller.String()) if ok { - panic("address already registered") + 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. - if !reName.MatchString(name) { + // 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 { @@ -240,10 +260,31 @@ func Resolve(input users.AddressOrName) std.Address { 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 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 index e8a7caff58d..c33edc32985 100644 --- a/examples/gno.land/r/demo/users/z_0_b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_0_b_filetest.gno @@ -1,10 +1,8 @@ package main -// SEND: 199000000ugnot +// SEND: 19900000ugnot import ( - "std" - "gno.land/r/demo/users" ) @@ -14,4 +12,4 @@ func main() { } // Error: -// payment must not be less than 200000000 +// 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 index 45d789b1a3e..cbb2e9209f4 100644 --- a/examples/gno.land/r/demo/users/z_0_filetest.gno +++ b/examples/gno.land/r/demo/users/z_0_filetest.gno @@ -7,10 +7,10 @@ import ( ) func main() { - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) users.Register("", "gnouser", "my profile") println("done") } // Error: -// invalid coin denominations: dontcare +// 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 index 0065d907e47..078058c0703 100644 --- a/examples/gno.land/r/demo/users/z_10_filetest.gno +++ b/examples/gno.land/r/demo/users/z_10_filetest.gno @@ -1,4 +1,4 @@ -// PKGPATH: gno.land/r/users_test +// PKGPATH: gno.land/r/demo/users_test package users_test import ( @@ -8,7 +8,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func init() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_11_filetest.gno b/examples/gno.land/r/demo/users/z_11_filetest.gno new file mode 100644 index 00000000000..603d63f371d --- /dev/null +++ b/examples/gno.land/r/demo/users/z_11_filetest.gno @@ -0,0 +1,25 @@ +package main + +// SEND: 200000000ugnot + +import ( + "std" + + "gno.land/r/demo/users" +) + +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") + +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 new file mode 100644 index 00000000000..5e661e8f8c1 --- /dev/null +++ b/examples/gno.land/r/demo/users/z_11b_filetest.gno @@ -0,0 +1,28 @@ +package main + +// SEND: 200000000ugnot + +import ( + "std" + + "gno.land/r/demo/users" +) + +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") + +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_1_filetest.gno b/examples/gno.land/r/demo/users/z_1_filetest.gno index a1c7e682022..504a0c7c3f9 100644 --- a/examples/gno.land/r/demo/users/z_1_filetest.gno +++ b/examples/gno.land/r/demo/users/z_1_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "gno.land/r/demo/users" diff --git a/examples/gno.land/r/demo/users/z_2_filetest.gno b/examples/gno.land/r/demo/users/z_2_filetest.gno index a99e8fd12ee..84b62a7e483 100644 --- a/examples/gno.land/r/demo/users/z_2_filetest.gno +++ b/examples/gno.land/r/demo/users/z_2_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_3_filetest.gno b/examples/gno.land/r/demo/users/z_3_filetest.gno index 1cd3886152a..ce34c6bba66 100644 --- a/examples/gno.land/r/demo/users/z_3_filetest.gno +++ b/examples/gno.land/r/demo/users/z_3_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_4_filetest.gno b/examples/gno.land/r/demo/users/z_4_filetest.gno index 51bd255e3e7..1a46d915c96 100644 --- a/examples/gno.land/r/demo/users/z_4_filetest.gno +++ b/examples/gno.land/r/demo/users/z_4_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_5_filetest.gno b/examples/gno.land/r/demo/users/z_5_filetest.gno index 5a96eec5322..4ab68ec0e0b 100644 --- a/examples/gno.land/r/demo/users/z_5_filetest.gno +++ b/examples/gno.land/r/demo/users/z_5_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main @@ -36,8 +36,17 @@ func main() { } // Output: -// * [gnouser](/r/demo/users:gnouser) +// * [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) +// * [x](/r/demo/users:x) // // ======================================== // ## user gnouser diff --git a/examples/gno.land/r/demo/users/z_6_filetest.gno b/examples/gno.land/r/demo/users/z_6_filetest.gno index bc6d747ebbe..85305fff1ad 100644 --- a/examples/gno.land/r/demo/users/z_6_filetest.gno +++ b/examples/gno.land/r/demo/users/z_6_filetest.gno @@ -3,11 +3,10 @@ package main import ( "std" - "gno.land/p/demo/testutils" "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() diff --git a/examples/gno.land/r/demo/users/z_7_filetest.gno b/examples/gno.land/r/demo/users/z_7_filetest.gno index 60efc3b91ea..3332ab49af4 100644 --- a/examples/gno.land/r/demo/users/z_7_filetest.gno +++ b/examples/gno.land/r/demo/users/z_7_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_7b_filetest.gno b/examples/gno.land/r/demo/users/z_7b_filetest.gno index 81f1076b3d1..60a397abe79 100644 --- a/examples/gno.land/r/demo/users/z_7b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_7b_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_8_filetest.gno b/examples/gno.land/r/demo/users/z_8_filetest.gno index 56c13862257..1eaa017b7d2 100644 --- a/examples/gno.land/r/demo/users/z_8_filetest.gno +++ b/examples/gno.land/r/demo/users/z_8_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "std" @@ -9,7 +9,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/users/z_9_filetest.gno b/examples/gno.land/r/demo/users/z_9_filetest.gno index e77a68d7ff9..2bd9bf555dc 100644 --- a/examples/gno.land/r/demo/users/z_9_filetest.gno +++ b/examples/gno.land/r/demo/users/z_9_filetest.gno @@ -7,7 +7,7 @@ import ( "gno.land/r/demo/users" ) -const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +const admin = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") func main() { caller := std.GetOrigCaller() // main diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index 4896d23499e..e1028530c8c 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -6,17 +6,13 @@ import ( "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" - - "gno.land/r/demo/users" - pusers "gno.land/p/demo/users" + "gno.land/r/demo/users" ) var ( - // wugnot is the admin token, able to mint and burn. - wugnot *grc20.AdminToken = grc20.NewAdminToken("wrapped GNOT", "wugnot", 0) - // WUGNOT is the banker usable by users directly. - WUGNOT = wugnot.GRC20() + banker *grc20.Banker = grc20.NewBanker("wrapped GNOT", "wugnot", 0) + Token = banker.Token() ) const ( @@ -24,106 +20,82 @@ const ( wugnotMinDeposit uint64 = 1 ) -// wrapper. -// - func Deposit() { caller := std.PrevRealm().Addr() sent := std.GetOrigSend() amount := sent.AmountOf("ugnot") - if uint64(amount) < ugnotMinDeposit { - panic(ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit)) - } - wugnot.Mint(caller, uint64(amount)) + require(uint64(amount) >= ugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit)) + checkErr(banker.Mint(caller, uint64(amount))) } func Withdraw(amount uint64) { - if amount < wugnotMinDeposit { - panic(ufmt.Sprintf("Deposit below minimum: %d/%d wugnot.", amount, wugnotMinDeposit)) - } + require(amount >= wugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d wugnot.", amount, wugnotMinDeposit)) caller := std.PrevRealm().Addr() pkgaddr := std.CurrentRealm().Addr() + callerBal := Token.BalanceOf(caller) + require(amount <= callerBal, ufmt.Sprintf("Insufficient balance: %d available, %d needed.", callerBal, amount)) - callerBal, _ := wugnot.BalanceOf(caller) - if callerBal < amount { - panic(ufmt.Sprintf("Insufficient balance: %d available, %d needed.", callerBal, amount)) - } - - // send swapped ugnots to caller - banker := std.GetBanker(std.BankerTypeRealmSend) + // send swapped ugnots to qcaller + stdBanker := std.GetBanker(std.BankerTypeRealmSend) send := std.Coins{{"ugnot", int64(amount)}} - banker.SendCoins(pkgaddr, caller, send) - wugnot.Burn(caller, amount) + stdBanker.SendCoins(pkgaddr, caller, send) + checkErr(banker.Burn(caller, amount)) } -// render. -// - func Render(path string) string { parts := strings.Split(path, "/") c := len(parts) switch { case path == "": - return wugnot.RenderHome() + return banker.RenderHome() case c == 2 && parts[0] == "balance": owner := std.Address(parts[1]) - balance, _ := wugnot.BalanceOf(owner) - return ufmt.Sprintf("%d\n", balance) + balance := Token.BalanceOf(owner) + return ufmt.Sprintf("%d", balance) default: - return "404\n" + return "404" } } -// XXX: if we could call WUGNOT.XXX instead of XXX from gnokey, then, all the following lines would not be needed. - -// direct getters. -// XXX: remove them in favor of q_call wugnot.XXX - -func TotalSupply() uint64 { - return wugnot.TotalSupply() -} +func TotalSupply() uint64 { return Token.TotalSupply() } func BalanceOf(owner pusers.AddressOrName) uint64 { - balance, err := wugnot.BalanceOf(users.Resolve(owner)) - if err != nil { - panic(err) - } - return balance + ownerAddr := users.Resolve(owner) + return Token.BalanceOf(ownerAddr) } func Allowance(owner, spender pusers.AddressOrName) uint64 { - allowance, err := wugnot.Allowance(users.Resolve(owner), users.Resolve(spender)) - if err != nil { - panic(err) - } - return allowance + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return Token.Allowance(ownerAddr, spenderAddr) } -// setters. -// - func Transfer(to pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - err := wugnot.Transfer(caller, users.Resolve(to), amount) - if err != nil { - panic(err) - } + toAddr := users.Resolve(to) + checkErr(Token.Transfer(toAddr, amount)) } func Approve(spender pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - err := wugnot.Approve(caller, users.Resolve(spender), amount) - if err != nil { - panic(err) - } + spenderAddr := users.Resolve(spender) + checkErr(Token.Approve(spenderAddr, amount)) } func TransferFrom(from, to pusers.AddressOrName, amount uint64) { - caller := std.PrevRealm().Addr() - err := wugnot.TransferFrom(caller, users.Resolve(from), users.Resolve(to), amount) + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(Token.TransferFrom(fromAddr, toAddr, amount)) +} + +func require(condition bool, msg string) { + if !condition { + panic(msg) + } +} + +func checkErr(err error) { if err != nil { panic(err) } diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno index 6eb47d7636d..bef65c03b68 100644 --- a/examples/gno.land/r/demo/wugnot/z0_filetest.gno +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -41,16 +41,10 @@ func printBalances() { printSingleBalance := func(name string, addr std.Address) { wugnotBal := wugnot.BalanceOf(pusers.AddressOrName(addr)) std.TestSetOrigCaller(addr) - abanker := std.GetBanker(std.BankerTypeOrigSend) - acoins := abanker.GetCoins(addr).AmountOf("ugnot") - bbanker := std.GetBanker(std.BankerTypeRealmIssue) - bcoins := bbanker.GetCoins(addr).AmountOf("ugnot") - cbanker := std.GetBanker(std.BankerTypeRealmSend) - ccoins := cbanker.GetCoins(addr).AmountOf("ugnot") - dbanker := std.GetBanker(std.BankerTypeReadonly) - dcoins := dbanker.GetCoins(addr).AmountOf("ugnot") - fmt.Printf("| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d %-9d %-9d %-9d |\n", - name, addr, wugnotBal, acoins, bcoins, ccoins, dcoins) + robanker := std.GetBanker(std.BankerTypeReadonly) + coins := robanker.GetCoins(addr).AmountOf("ugnot") + fmt.Printf("| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d |\n", + name, addr, wugnotBal, coins) } println("-----------") printSingleBalance("wugnot_test", addrt) @@ -61,17 +55,17 @@ func printBalances() { // Output: // ----------- -// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=0 | ugnot=200000000 200000000 200000000 200000000 | -// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | -// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=0 | ugnot=200000000 | +// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 | +// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 | // ----------- // ----------- -// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=123400 | ugnot=200000000 200000000 200000000 200000000 | -// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | -// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=123400 | ugnot=200000000 | +// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 | +// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 | // ----------- // ----------- -// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=119158 | ugnot=200004242 200004242 200004242 200004242 | -// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=99995759 99995759 99995759 99995759 | -// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=119158 | ugnot=200004242 | +// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=99995759 | +// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 | // ----------- diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index f615e26e491..08b0911cf24 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -5,6 +5,8 @@ import ( "strings" "gno.land/p/demo/avl" + "gno.land/p/demo/context" + "gno.land/p/gov/proposal" ) var ( @@ -39,11 +41,19 @@ func AdminRemoveModerator(addr std.Address) { moderatorList.Set(addr.String(), false) // FIXME: delete instead? } +func DaoAddPost(ctx context.Context, slug, title, body, publicationDate, authors, tags string) { + proposal.AssertContextApprovedByGovDAO(ctx) + caller := std.DerivePkgAddr("gno.land/r/gov/dao") + addPost(caller, slug, title, body, publicationDate, authors, tags) +} + func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.GetOrigCaller() + addPost(caller, slug, title, body, publicationDate, authors, tags) +} +func addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) { var tagList []string if tags != "" { tagList = strings.Split(tags, ",") @@ -68,6 +78,12 @@ func ModEditPost(slug, title, body, publicationDate, authors, tags string) { checkErr(err) } +func ModRemovePost(slug string) { + assertIsModerator() + + b.RemovePost(slug) +} + func ModAddCommenter(addr std.Address) { assertIsModerator() commenterList.Set(addr.String(), true) diff --git a/examples/gno.land/r/gnoland/blog/gno.mod b/examples/gno.land/r/gnoland/blog/gno.mod index 1d64238cdc8..17c17e0cfa6 100644 --- a/examples/gno.land/r/gnoland/blog/gno.mod +++ b/examples/gno.land/r/gnoland/blog/gno.mod @@ -3,4 +3,6 @@ module gno.land/r/gnoland/blog require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/blog v0.0.0-latest + gno.land/p/demo/context v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest ) diff --git a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno index 1be61138b39..15688ca4bc7 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno @@ -47,6 +47,8 @@ No posts. { got := Render("p/slug2") expected := ` +
+ # title2 body2 @@ -63,6 +65,7 @@ Published by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog
Comment section
+
` assertMDEquals(t, got, expected) @@ -95,7 +98,8 @@ Published by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog AddComment("slug2", "comment4") AddComment("slug1", "comment5") got := Render("p/slug2") - expected := ` + expected := `
+ # title2 body2 @@ -124,16 +128,21 @@ Published by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog --- +
` assertMDEquals(t, got, expected) } // edit post. { - ModEditPost("slug2", "title2++", "body2++", "2009-11-10T23:00:00Z", "manfred", "tag1,tag4") + oldTitle := "title2" + oldDate := "2022-05-20T13:17:23Z" + + ModEditPost("slug2", oldTitle, "body2++", oldDate, "manfred", "tag1,tag4") got := Render("p/slug2") - expected := ` -# title2++ + expected := `
+ +# title2 body2++ @@ -141,7 +150,7 @@ body2++ Tags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4) -Written by manfred on 10 Nov 2009 +Written by manfred on 20 May 2022 Published by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog @@ -161,8 +170,46 @@ Published by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog --- +
` assertMDEquals(t, got, expected) + + home := Render("") + + if strings.Count(home, oldTitle) != 1 { + t.Errorf("post not edited properly") + } + // Edits work everything except title, slug, and publicationDate + // Edits to the above will cause duplication on the blog home page + } + + { // Test remove functionality + title := "example title" + slug := "testSlug1" + ModAddPost(slug, title, "body1", "2022-05-25T13:17:22Z", "moul", "tag1,tag2") + + got := Render("") + + if !strings.Contains(got, title) { + t.Errorf("post was not added properly") + } + + postRender := Render("p/" + slug) + + if !strings.Contains(postRender, title) { + t.Errorf("post not rendered properly") + } + + ModRemovePost(slug) + got = Render("") + + if strings.Contains(got, title) { + t.Errorf("post was not removed") + } + + postRender = Render("p/" + slug) + + assertMDEquals(t, postRender, "404") } // TODO: pagination. diff --git a/examples/gno.land/r/gnoland/pages/page_events.gno b/examples/gno.land/r/gnoland/events/events.gno similarity index 59% rename from examples/gno.land/r/gnoland/pages/page_events.gno rename to examples/gno.land/r/gnoland/events/events.gno index 9728319dad8..9c2708a112e 100644 --- a/examples/gno.land/r/gnoland/pages/page_events.gno +++ b/examples/gno.land/r/gnoland/events/events.gno @@ -1,20 +1,38 @@ -package gnopages +package events -func init() { - var ( - path = "events" - title = "Gno.land Core Team Attends Industry Events & Meetups" - // XXX: description = "If you’re interested in learning more about Gno.land, you can join us at major blockchain industry events throughout the year either in person or virtually." - body = ` -If you’re interested in building web3 with us, catch up with Gno.land in person at one of our industry events. -We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform. +import ( + "gno.land/p/demo/ui" +) ---- +// 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 -## Upcoming Events +func Render(_ string) string { + dom := ui.DOM{Prefix: "r/gnoland/events:"} + dom.Title = "Gno.land Core Team Attends Industry Events & Meetups" + dom.Classes = []string{"gno-tmpl-section"} -
+ // body + dom.Body.Append(introSection()...) + dom.Body.Append(ui.HR{}) + dom.Body.Append(upcomingEvents()...) + dom.Body.Append(ui.HR{}) + dom.Body.Append(pastEvents()...) + return dom.String() +} + +func introSection() ui.Element { + return ui.Element{ + ui.Paragraph("If you’re interested in building web3 with us, catch up with gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform."), + } +} + +func upcomingEvents() ui.Element { + return ui.Element{ + ui.H2("Upcoming Events"), + ui.Text(`
### GopherCon EU @@ -43,13 +61,54 @@ We’re looking to connect with developers and like-minded thinkers who can cont [Learn More](https://nebular.builders/)
-
+
+ +
+
+ +
+
`), + } +} + +func pastEvents() ui.Element { + return ui.Element{ + ui.H2("Past Events"), + ui.Text(`
+ +
+ +### Gno @ Golang Serbia + +- **Join the meetup** +- Belgrade, May 23, 2024 ---- +[Learn more](https://gno.land/r/gnoland/blog:p/gnomes-in-serbia) -## Past Events +
+ +
+ +### Intro to Gno Tokyo + +- **Join the meetup** +- Tokyo, April 11, 2024 + +[Learn more](https://gno.land/r/gnoland/blog:p/gno-tokyo) + +
+ +
+ +### Go to Gno Seoul + +- **Join the workshop** +- Seoul, March 23, 2024 + +[Learn more](https://medium.com/onbloc/go-to-gno-recap-intro-to-the-gno-stack-with-memeland-284a43d7f620) + +
-
### GopherCon US @@ -176,7 +235,6 @@ We’re looking to connect with developers and like-minded thinkers who can cont [Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI)
-
` - ) - _ = b.NewPost("", path, title, body, "2022-05-20T13:17:24Z", nil, nil) +
`), + } } diff --git a/examples/gno.land/r/gnoland/events/events_filetest.gno b/examples/gno.land/r/gnoland/events/events_filetest.gno new file mode 100644 index 00000000000..46ee273414d --- /dev/null +++ b/examples/gno.land/r/gnoland/events/events_filetest.gno @@ -0,0 +1,226 @@ +package main + +import "gno.land/r/gnoland/events" + +func main() { + println(events.Render("")) +} + +// Output: +//
+// +// # Gno.land Core Team Attends Industry Events & Meetups +// +// +// If you’re interested in building web3 with us, catch up with gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform. +// +// +// --- +// +// ## Upcoming Events +// +//
+//
+// +// ### GopherCon EU +// - Come Meet Us at our Booth +// - Berlin, June 17 - 20, 2024 +// +// [Learn More](https://gophercon.eu/) +//
+// +//
+// +// ### GopherCon US +// - Come Meet Us at our Booth +// - Chicago, July 7 - 10, 2024 +// +// [Learn More](https://www.gophercon.com/) +// +//
+// +//
+// +// ### Nebular Summit +// - Join our workshop +// - Brussels, July 12 - 13, 2024 +// +// [Learn More](https://nebular.builders/) +//
+// +//
+// +//
+//
+// +//
+//
+// +// --- +// +// ## Past Events +// +//
+// +//
+// +// ### Gno @ Golang Serbia +// +// - **Join the meetup** +// - Belgrade, May 23, 2024 +// +// [Learn more](https://gno.land/r/gnoland/blog:p/gnomes-in-serbia) +// +//
+// +//
+// +// ### Intro to Gno Tokyo +// +// - **Join the meetup** +// - Tokyo, April 11, 2024 +// +// [Learn more](https://gno.land/r/gnoland/blog:p/gno-tokyo) +// +//
+// +//
+// +// ### Go to Gno Seoul +// +// - **Join the workshop** +// - Seoul, March 23, 2024 +// +// [Learn more](https://medium.com/onbloc/go-to-gno-recap-intro-to-the-gno-stack-with-memeland-284a43d7f620) +// +//
+// +//
+// +// ### GopherCon US +// +// - **Come Meet Us at our Booth** +// - San Diego, September 26 - 29, 2023 +// +// [Learn more](https://www.gophercon.com/) +// +//
+// +//
+// +// ### GopherCon EU +// +// - **Come Meet Us at our Booth** +// - Berlin, July 26 - 29, 2023 +// +// [Learn more](https://gophercon.eu/) +// +//
+// +//
+// +// ### Nebular Summit Gno.land for Developers +// +// - Paris, July 24 - 25, 2023 +// - Manfred Touron +// +// [Learn more](https://www.nebular.builders/) +// +//
+// +//
+// +// ### EthCC +// +// - **Come Meet Us at our Booth** +// - Paris, July 17 - 20, 2023 +// - Manfred Touron +// +// [Learn more](https://www.ethcc.io/) +// +//
+// +//
+// +// ### Eth Seoul +// +// - **The Evolution of Smart Contracts: A Journey into Gno.land** +// - Seoul, June 3, 2023 +// - Manfred Touron +// +// [Learn more](https://2023.ethseoul.org/) +// +//
+//
+// +// ### BUIDL Asia +// +// - **Proof of Contribution in Gno.land** +// - Seoul, June 6, 2023 +// - Manfred Touron +// +// [Learn more](https://www.buidl.asia/) +// +//
+//
+// +// ### Game Developer Conference +// +// - **Side Event: Web3 Gaming Apps Powered by Gno** +// - San Francisco, Mach 23, 2023 +// - Jae Kwon +// +// [Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) +// +//
+//
+// +// ### EthDenver +// +// - **Side Event: Discover Gno.land** +// - Denver, Feb 24 - Mar 5, 2023 +// - Jae Kwon +// +// [Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) +// +//
+//
+// +// ### Istanbul Blockchain Week +// +// - Istanbul, Nov 14 - 17, 2022 +// - Manfred Touron +// +// [Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4) +// +//
+//
+// +// ### Web Summit Buckle Up and Build with Cosmos +// +// - Lisbon, Nov 1 - 4, 2022 +// - Manfred Touron +// +//
+//
+// +// ### Cosmoverse +// +// - Medallin, Sept 26 - 28, 2022 +// - Manfred Touron +// +// [Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk) +// +//
+//
+// +// ### Berlin Blockchain Week Buckle Up and Build with Cosmos +// +// - Berlin, Sept 11 - 18, 2022 +// +// [Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI) +// +//
+//
+// +//
diff --git a/examples/gno.land/r/gnoland/events/gno.mod b/examples/gno.land/r/gnoland/events/gno.mod new file mode 100644 index 00000000000..ec781c7cf10 --- /dev/null +++ b/examples/gno.land/r/gnoland/events/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/gnoland/events + +require gno.land/p/demo/ui v0.0.0-latest diff --git a/examples/gno.land/r/gnoland/faucet/admin.gno b/examples/gno.land/r/gnoland/faucet/admin.gno index a4916c5af02..37108059b74 100644 --- a/examples/gno.land/r/gnoland/faucet/admin.gno +++ b/examples/gno.land/r/gnoland/faucet/admin.gno @@ -25,7 +25,7 @@ func AdminSetTransferLimit(amount int64) string { if err := assertIsAdmin(); err != nil { return err.Error() } - gLimit = std.Coin{Denom: "ugnot", Amount: amount} + gLimit = std.NewCoin("ugnot", amount) return "" } diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index 038c7283fa1..908b86d4aaf 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -21,7 +21,7 @@ var ( gTotalTransfers = uint(0) // per request limit, 350 gnot - gLimit std.Coin = std.Coin{"ugnot", 350000000} + gLimit std.Coin = std.NewCoin("ugnot", 350000000) ) func Transfer(to std.Address, send int64) string { @@ -37,7 +37,7 @@ func Transfer(to std.Address, send int64) string { if send > gLimit.Amount { return errors.New("Per request limit " + gLimit.String() + " exceed").Error() } - sendCoins := std.Coins{std.Coin{Denom: "ugnot", Amount: send}} + sendCoins := std.Coins{std.NewCoin("ugnot", send)} gTotalTransferred = gTotalTransferred.Add(sendCoins) gTotalTransfers++ diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 3f12bd0a7d6..d9c399942ae 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -2,7 +2,6 @@ package ghverify import ( "std" - "strings" "testing" "gno.land/p/demo/testutils" diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod index 2864958930c..cb2ec58b665 100644 --- a/examples/gno.land/r/gnoland/home/gno.mod +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -1,6 +1,7 @@ module gno.land/r/gnoland/home require ( + gno.land/p/demo/ownable v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/demo/ui v0.0.0-latest gno.land/r/gnoland/blog v0.0.0-latest diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index d8bec8242c2..62984711d79 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -3,6 +3,7 @@ package home import ( "std" + "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" "gno.land/p/demo/ui" blog "gno.land/r/gnoland/blog" @@ -12,9 +13,19 @@ import ( // XXX: use an updatable block system to update content from a DAO // XXX: var blocks avl.Tree +var ( + override string + admin = ownable.NewWithAddress("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") // @manfred by default +) + func Render(_ string) string { + if override != "" { + return override + } + dom := ui.DOM{Prefix: "r/gnoland/home:"} - dom.Title = "Welcome to Gno.land" + dom.Title = "Welcome to gno.land" + dom.Classes = []string{"gno-tmpl-section"} // body dom.Body.Append(introSection()...) @@ -80,9 +91,9 @@ func upcomingEvents(limit int) ui.Element { func introSection() ui.Element { return ui.Element{ - ui.H3("We’re building Gno.land, the first open-source smart contract system, 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."), + ui.H3("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."), } } @@ -156,7 +167,7 @@ func playgroundSection() ui.Element { ui.H3("[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.`), +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)."), } } @@ -194,7 +205,8 @@ func packageStaffPicks() ui.Element { ui.Link{URL: "r/demo/microblog"}, ui.Link{URL: "r/demo/nft"}, ui.Link{URL: "r/demo/types"}, - ui.Link{URL: "r/demo/art"}, + ui.Link{URL: "r/demo/art/gnoface"}, + ui.Link{URL: "r/demo/art/millipede"}, ui.Link{URL: "r/demo/groups"}, ui.Text("..."), }, @@ -224,7 +236,7 @@ func discoverLinks() ui.Element { ui.Text(`
-### Learn about Gno.land +### Learn about gno.land - [About](/about) - [GitHub](https://github.com/gnolang) @@ -233,6 +245,7 @@ func discoverLinks() ui.Element { - Tokenomics (soon) - [Partners, Fund, Grants](/partners) - [Explore the Ecosystem](/ecosystem) +- [Careers](https://jobs.lever.co/allinbits?department=Gno.land)
@@ -245,6 +258,7 @@ func discoverLinks() ui.Element { - [Visit the official documentation](https://docs.gno.land) - [Gno by Example](https://gno-by-example.com/) - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev) +- [Get testnet GNOTs](https://faucet.gno.land)
@@ -253,7 +267,7 @@ func discoverLinks() ui.Element { - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) - [Gnoscan](https://gnoscan.io) -- [Portal Loop](https://docs.gno.land/concepts/portal-loop) +- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - Testnet 4 (upcoming) - [Testnet 3](https://test3.gno.land/) (archive) - [Testnet 2](https://test2.gno.land/) (archive) @@ -263,3 +277,13 @@ func discoverLinks() ui.Element {
`), } } + +func AdminSetOverride(content string) { + admin.AssertCallerIsOwner() + override = content +} + +func AdminTransferOwnership(newAdmin std.Address) { + admin.AssertCallerIsOwner() + admin.TransferOwnership(newAdmin) +} diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index fa6fbe4e2a1..2b0a802718f 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -7,186 +7,193 @@ func main() { } // Output: -//# Welcome to Gno.land +//
// -//### We’re building Gno.land, the first open-source smart contract system, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts. +// # 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. // +// 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. // -//
+// 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 +//
+//
// -//- [About](/about) -//- [GitHub](https://github.com/gnolang) -//- [Blog](/blog) -//- [Events](/events) -//- Tokenomics (soon) -//- [Partners, Fund, Grants](/partners) -//- [Explore the Ecosystem](/ecosystem) +// ### Learn about gno.land // -//
+// - [About](/about) +// - [GitHub](https://github.com/gnolang) +// - [Blog](/blog) +// - [Events](/events) +// - Tokenomics (soon) +// - [Partners, Fund, Grants](/partners) +// - [Explore the Ecosystem](/ecosystem) +// - [Careers](https://jobs.lever.co/allinbits?department=Gno.land) // -//
+//
// -//### Build with Gno +//
// -//- [Write Gno in the browser](https://play.gno.land) -//- [Read about the Gno Language](/gnolang) -//- [Visit the official documentation](https://docs.gno.land) -//- [Gno by Example](https://gno-by-example.com/) -//- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev) +// ### Build with Gno // -//
-//
+// - [Write Gno in the browser](https://play.gno.land) +// - [Read about the Gno Language](/gnolang) +// - [Visit the official documentation](https://docs.gno.land) +// - [Gno by Example](https://gno-by-example.com/) +// - [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) -//- [Gnoscan](https://gnoscan.io) -//- [Portal Loop](https://docs.gno.land/concepts/portal-loop) -//- Testnet 4 (upcoming) -//- [Testnet 3](https://test3.gno.land/) (archive) -//- [Testnet 2](https://test2.gno.land/) (archive) -//- Testnet Faucet Hub (soon) +// ### Explore the universe // -//
-//
-//
+// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +// - [Gnoscan](https://gnoscan.io) +// - [Portal Loop](https://docs.gno.land/concepts/portal-loop) +// - Testnet 4 (upcoming) +// - [Testnet 3](https://test3.gno.land/) (archive) +// - [Testnet 2](https://test2.gno.land/) (archive) +// - Testnet Faucet Hub (soon) // -//
-//
+//
+//
+//
// -//### Latest Blogposts +//
+//
// -//No posts. -//
-//
+// ### Latest Blogposts // -//### Upcoming Events +// No posts. +//
+//
// -//[View upcoming events](/events) -//
-//
+// ### Upcoming Events // -//### Latest Contributions +// [View upcoming events](/events) +//
+//
// -//[View latest contributions](https://github.com/gnolang/gno/pulls) -//
-//
+// ### Latest Contributions // +// [View latest contributions](https://github.com/gnolang/gno/pulls) +//
+//
// -//--- // -//### [Gno Playground](https://play.gno.land) +// --- // +// ### [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 +// --- // -//
-//
+// ### 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](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland) // -//#### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys) +// - [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/sys/names](r/sys/names) -//- [r/sys/rewards](r/sys/rewards) -//- [r/sys/validators](r/sys/validators) +// #### [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/validators](r/sys/validators) // -//#### [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) -//- [r/demo/microblog](r/demo/microblog) -//- [r/demo/nft](r/demo/nft) -//- [r/demo/types](r/demo/types) -//- [r/demo/art](r/demo/art) -//- [r/demo/groups](r/demo/groups) -//- ... +// #### [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) +// - [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) -//- ... +// #### [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) +// - ... // +//
+//
// -//--- // -//### Contributions (WorxDAO & GoR) +// --- // -//coming soon +// ### Contributions (WorxDAO & GoR) // -//--- +// coming soon // +// --- // -//
-//
// -//### Socials +//
+//
// -//- Check out our [community projects](https://github.com/gnolang/awesome-gno) -//- ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn) -//- ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland) -//- ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland) -//- ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland) +// ### Socials // -//
-//
+// - Check out our [community projects](https://github.com/gnolang/awesome-gno) +// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn) +// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland) +// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland) +// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland) // -//### Quote of the ~Day~ Block#123 +//
+//
// -//> Now, you Gno. +// ### Quote of the ~Day~ Block#123 // -//
-//
+// > Now, you Gno. // +//
+//
// -//--- // -//**This is a testnet.** -//Package names are not guaranteed to be available for production. +// --- +// +// **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/overide_filetest.gno new file mode 100644 index 00000000000..34356b93349 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/overide_filetest.gno @@ -0,0 +1,24 @@ +package main + +import ( + "std" + + "gno.land/p/demo/testutils" + "gno.land/r/gnoland/home" +) + +func main() { + std.TestSetOrigCaller("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") + home.AdminSetOverride("Hello World!") + println(home.Render("")) + home.AdminTransferOwnership(testutils.TestAddress("newAdmin")) + defer func() { + r := recover() + println("r: ", r) + }() + home.AdminSetOverride("Not admin anymore") +} + +// Output: +// Hello World! +// r: unauthorized; caller is not owner diff --git a/examples/gno.land/r/gnoland/monit/gno.mod b/examples/gno.land/r/gnoland/monit/gno.mod new file mode 100644 index 00000000000..e67fdaa7d71 --- /dev/null +++ b/examples/gno.land/r/gnoland/monit/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gnoland/monit + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/watchdog v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/monit/monit.gno b/examples/gno.land/r/gnoland/monit/monit.gno new file mode 100644 index 00000000000..8747ea582b3 --- /dev/null +++ b/examples/gno.land/r/gnoland/monit/monit.gno @@ -0,0 +1,59 @@ +// Package monit links a monitoring system with the chain in both directions. +// +// The agent will periodically call Incr() and verify that the value is always +// higher than the previously known one. The contract will store the last update +// time and use it to detect whether or not the monitoring agent is functioning +// correctly. +package monit + +import ( + "std" + "time" + + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/watchdog" +) + +var ( + counter int + lastUpdate time.Time + lastCaller std.Address + wd = watchdog.Watchdog{Duration: 5 * time.Minute} + owner = ownable.New() // TODO: replace with -> ownable.NewWithAddress... + watchdogDuration = 5 * time.Minute +) + +// Incr increments the counter and informs the watchdog that we're alive. +// This function can be called by anyone. +func Incr() int { + counter++ + lastUpdate = time.Now() + lastCaller = std.PrevRealm().Addr() + wd.Alive() + return counter +} + +// Reset resets the realm state. +// This function can only be called by the admin. +func Reset() { + if owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner + panic("unauthorized") + } + counter = 0 + lastCaller = std.PrevRealm().Addr() + lastUpdate = time.Now() + wd = watchdog.Watchdog{Duration: 5 * time.Minute} +} + +func Render(_ string) string { + status := wd.Status() + return ufmt.Sprintf( + "counter=%d\nlast update=%s\nlast caller=%s\nstatus=%s", + counter, lastUpdate, lastCaller, status, + ) +} + +// TransferOwnership transfers ownership to a new owner. This is a proxy to +// ownable.Ownable.TransferOwnership. +func TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) } diff --git a/examples/gno.land/r/gnoland/monit/monit_test.gno b/examples/gno.land/r/gnoland/monit/monit_test.gno new file mode 100644 index 00000000000..fc9b394b8ed --- /dev/null +++ b/examples/gno.land/r/gnoland/monit/monit_test.gno @@ -0,0 +1,56 @@ +package monit + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestPackage(t *testing.T) { + // initial state, watchdog is KO. + { + expected := `counter=0 +last update=0001-01-01 00:00:00 +0000 UTC +last caller= +status=KO` + got := Render("") + uassert.Equal(t, expected, got) + } + + // call Incr(), watchdog is OK. + Incr() + Incr() + Incr() + { + expected := `counter=3 +last update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 +last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +status=OK` + got := Render("") + uassert.Equal(t, expected, got) + } + + /* XXX: improve tests once we've the missing std.TestSkipTime feature + // wait 1h, watchdog is KO. + use std.TestSkipTime(time.Hour) + { + expected := `counter=3 + last update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001 + last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm + status=KO` + got := Render("") + uassert.Equal(t, expected, got) + } + + // call Incr(), watchdog is OK. + Incr() + { + expected := `counter=4 + last update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 + last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm + status=OK` + got := Render("") + uassert.Equal(t, expected, got) + } + */ +} diff --git a/examples/gno.land/r/gnoland/pages/page_about.gno b/examples/gno.land/r/gnoland/pages/page_about.gno index dbe269044de..6b1f5a6c556 100644 --- a/examples/gno.land/r/gnoland/pages/page_about.gno +++ b/examples/gno.land/r/gnoland/pages/page_about.gno @@ -3,26 +3,26 @@ package gnopages func init() { path := "about" title := "Gno.land Is A Platform To Write Smart Contracts In Gno" - // XXX: description := "On Gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem." + // XXX: description := "On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem." body := ` Gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go -programming language. On Gno.land, smart contracts can be uploaded on-chain only by publishing their full source code, +programming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code, making it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code -libraries on-chain, Gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent, +libraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse. Gno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of smart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive -to a single ecosystem or limited by design. Go developers can easily port their existing web apps to Gno.land or build +to a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build new ones from scratch, making web3 vastly more accessible. -Secured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, Gno.land prioritizes +Secured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes fairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that often corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and alignment. -One of our inspirations for Gno.land is the gospels, which built a system of moral code that lasted thousands of years. -By observing a minimal production implementation, Gno.land’s design will endure over time and serve as a reference for +One of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years. +By observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for future generations with censorship-resistant tools that improve their understanding of the world. ` _ = b.NewPost("", path, title, body, "2022-05-20T13:17:22Z", nil, nil) diff --git a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno index d4a9e7ae96e..e1a540c98a5 100644 --- a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno +++ b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno @@ -4,7 +4,7 @@ func init() { var ( path = "ecosystem" title = "Discover Gno.land Ecosystem Projects & Initiatives" - // XXX: description = "Dive further into the Gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building." + // XXX: description = "Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building." body = ` ### [Gno Playground](https://play.gno.land) @@ -16,21 +16,21 @@ Visit the playground at [play.gno.land](https://play.gno.land)! ### [Gnoscan](https://gnoscan.io) -Developed by the Onbloc team, Gnoscan is Gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find -information that resides on the Gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. +Developed by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find +information that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. Gnoscan makes our on-chain data easy to read and intuitive to discover. -Explore the Gno.land blockchain at [gnoscan.io](https://gnoscan.io)! +Explore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)! ### Adena -Adena is a user-friendly non-custodial wallet for Gno.land. Open-source and developed by Onbloc, Adena allows gnomes to +Adena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to interact easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a high-quality interface, support for NFTs and custom tokens, and seamless integration. ### Gnoswap -Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on Gno.land and is an +Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an automated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform. ### Flippando diff --git a/examples/gno.land/r/gnoland/pages/page_gnolang.gno b/examples/gno.land/r/gnoland/pages/page_gnolang.gno index 00c8d67fa54..13fc4072b1a 100644 --- a/examples/gno.land/r/gnoland/pages/page_gnolang.gno +++ b/examples/gno.land/r/gnoland/pages/page_gnolang.gno @@ -15,7 +15,7 @@ Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and th ![Gno and Go differences](static/img/gno-language/go-and-gno.jpg) -The composable nature of Go/Gno allows for type-checked interactions between contracts, making Gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on Gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts. +The composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts. ![Example of Gno code](static/img/gno-language/code-example.jpg) diff --git a/examples/gno.land/r/gnoland/pages/page_gor.gno b/examples/gno.land/r/gnoland/pages/page_gor.gno index fbe9089476f..d46e9cb0ccc 100644 --- a/examples/gno.land/r/gnoland/pages/page_gor.gno +++ b/examples/gno.land/r/gnoland/pages/page_gor.gno @@ -3,14 +3,14 @@ package gnopages func init() { path := "gor" title := "Game of Realms - A Contest For The Best Contributors" - // XXX: description := "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool." + // XXX: description := "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the gno.land platform with a 133,700 ATOM prize pool." body := `
### Game of Realms -The first high-stakes contest will see participants compete for tiered membership to co-own the Gno.land blockchain. A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. +The first high-stakes contest will see participants compete for tiered membership to co-own the gno.land blockchain. A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world.
@@ -18,7 +18,7 @@ The competition is currently in phase one – for advanced developers only. Once the necessary tools to start phase two are ready, we’ll open up the competition to newer devs and non-technical contributors. -If you want to stack ATOM rewards and play a key role in the success of Gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today. +If you want to stack ATOM rewards and play a key role in the success of gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today.
@@ -49,7 +49,7 @@ If you want to stack ATOM rewards and play a key role in the success of Gno.land ## Evaluation DAO -This complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the Gno.land platform are fairly rewarded. +This complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the gno.land platform are fairly rewarded.
@@ -101,7 +101,7 @@ See [GitHub issue 519](https://github.com/gnolang/gno/issues/519) for the most u ## Tutorials -To progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to Gno.land. +To progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to gno.land.
@@ -122,7 +122,7 @@ How to create, present, and house the tutorials has been established with produc diff --git a/examples/gno.land/r/gnoland/pages/page_license.gno b/examples/gno.land/r/gnoland/pages/page_license.gno index 6c477fb27e6..77bdd8cdaab 100644 --- a/examples/gno.land/r/gnoland/pages/page_license.gno +++ b/examples/gno.land/r/gnoland/pages/page_license.gno @@ -20,7 +20,7 @@ You should have received a copy of the GNO Network General Public License along with this program. If not, see . Attached below are the terms of the GNO Network General Public License, Version -4 (a fork of the GNU Afferro General Public License 3). +4 (a fork of the GNU Affero General Public License 3). ## Additional Terms @@ -409,7 +409,7 @@ that material) supplement the terms of this License with terms: - g) Requiring strong attribution such as notices on any user interfaces that run or convey any covered work, such as a prominent link to a URL on the header of a website, such that all users of the covered work may - become aware of the the notice, for a period no longer than 20 years. + become aware of the notice, for a period no longer than 20 years. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you diff --git a/examples/gno.land/r/gnoland/pages/page_partners.gno b/examples/gno.land/r/gnoland/pages/page_partners.gno index 74e45b8119f..e60802fef63 100644 --- a/examples/gno.land/r/gnoland/pages/page_partners.gno +++ b/examples/gno.land/r/gnoland/pages/page_partners.gno @@ -6,7 +6,7 @@ func init() { body := `### Fund and Grants Program Are you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, -or smart contract libraries on Gno.land, you can apply for a grant. The Gno.land Ecosystem Fund and Grants program +or smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program provides financial contributions for individuals and teams to innovate on the platform. Read more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants). diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno index 8019f88a36c..c7972686bb3 100644 --- a/examples/gno.land/r/gnoland/pages/pages_test.gno +++ b/examples/gno.land/r/gnoland/pages/pages_test.gno @@ -9,7 +9,6 @@ func TestHome(t *testing.T) { printedOnce := false got := Render("") expectedSubtrings := []string{ - "/r/gnoland/pages:p/events", "/r/gnoland/pages:p/tokenomics", "/r/gnoland/pages:p/start", "/r/gnoland/pages:p/gor", diff --git a/examples/gno.land/r/gnoland/valopers/gno.mod b/examples/gno.land/r/gnoland/valopers/gno.mod new file mode 100644 index 00000000000..2d24fb27952 --- /dev/null +++ b/examples/gno.land/r/gnoland/valopers/gno.mod @@ -0,0 +1,11 @@ +module gno.land/r/gnoland/valopers + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest + gno.land/r/gov/dao v0.0.0-latest + gno.land/r/sys/validators v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/valopers/init.gno b/examples/gno.land/r/gnoland/valopers/init.gno new file mode 100644 index 00000000000..eea36fcf0ce --- /dev/null +++ b/examples/gno.land/r/gnoland/valopers/init.gno @@ -0,0 +1,7 @@ +package valopers + +import "gno.land/p/demo/avl" + +func init() { + valopers = avl.NewTree() +} diff --git a/examples/gno.land/r/gnoland/valopers/valopers.gno b/examples/gno.land/r/gnoland/valopers/valopers.gno new file mode 100644 index 00000000000..74cec941e0d --- /dev/null +++ b/examples/gno.land/r/gnoland/valopers/valopers.gno @@ -0,0 +1,181 @@ +// 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. +package valopers + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" + pVals "gno.land/p/sys/validators" + govdao "gno.land/r/gov/dao" + "gno.land/r/sys/validators" +) + +const ( + errValoperExists = "valoper already exists" + errValoperMissing = "valoper does not exist" + errInvalidAddressUpdate = "valoper updated address exists" + errValoperNotCaller = "valoper is not the caller" +) + +// valopers keeps track of all the active validator operators +var valopers *avl.Tree // Address -> Valoper + +// Valoper represents a validator operator profile +type Valoper struct { + Name string // the display name of the valoper + Description string // the description of the valoper + + Address std.Address // The bech32 gno address of the validator + PubKey string // the bech32 public key of the validator + P2PAddresses []string // the publicly reachable P2P addresses of the validator + Active bool // flag indicating if the valoper is active +} + +// Register registers a new valoper +func Register(v Valoper) { + // Check if the valoper is already registered + if isValoper(v.Address) { + panic(errValoperExists) + } + + // TODO add address derivation from public key + // (when the laws of gno make it possible) + + // Save the valoper to the set + valopers.Set(v.Address.String(), v) +} + +// Update updates an existing valoper +func Update(address std.Address, v Valoper) { + // Check if the valoper is present + if !isValoper(address) { + panic(errValoperMissing) + } + + // Check that the valoper wouldn't be + // overwriting an existing one + isAddressUpdate := address != v.Address + if isAddressUpdate && isValoper(v.Address) { + panic(errInvalidAddressUpdate) + } + + // Remove the old valoper info + // in case the address changed + if address != v.Address { + valopers.Remove(address.String()) + } + + // Save the new valoper info + valopers.Set(v.Address.String(), v) +} + +// GetByAddr fetches the valoper using the address, if present +func GetByAddr(address std.Address) Valoper { + valoperRaw, exists := valopers.Get(address.String()) + if !exists { + panic(errValoperMissing) + } + + return valoperRaw.(Valoper) +} + +// Render renders the current valoper set +func Render(_ string) string { + if valopers.Size() == 0 { + return "No valopers to display." + } + + output := "Valset changes to apply:\n" + valopers.Iterate("", "", func(_ string, value interface{}) bool { + valoper := value.(Valoper) + + output += valoper.Render() + + return false + }) + + return output +} + +// Render renders a single valoper with their information +func (v Valoper) Render() string { + output := ufmt.Sprintf("## %s\n", v.Name) + output += ufmt.Sprintf("%s\n\n", v.Description) + output += ufmt.Sprintf("- Address: %s\n", v.Address.String()) + output += ufmt.Sprintf("- PubKey: %s\n", v.PubKey) + output += "- P2P Addresses: [\n" + + if len(v.P2PAddresses) == 0 { + output += "]\n" + + return output + } + + for index, addr := range v.P2PAddresses { + output += addr + + if index == len(v.P2PAddresses)-1 { + output += "]\n" + + continue + } + + output += ",\n" + } + + return output +} + +// isValoper checks if the valoper exists +func isValoper(address std.Address) bool { + _, exists := valopers.Get(address.String()) + + return exists +} + +// GovDAOProposal creates a proposal to the GovDAO +// for adding the given valoper to the validator set. +// This function is meant to serve as a helper +// for generating the govdao proposal +func GovDAOProposal(address std.Address) { + var ( + valoper = GetByAddr(address) + votingPower = uint64(1) + ) + + // Make sure the valoper is the caller + if std.GetOrigCaller() != address { + panic(errValoperNotCaller) + } + + // Determine the voting power + if !valoper.Active { + votingPower = uint64(0) + } + + changesFn := func() []pVals.Validator { + return []pVals.Validator{ + { + Address: valoper.Address, + PubKey: valoper.PubKey, + VotingPower: votingPower, + }, + } + } + + // Create the executor + executor := validators.NewPropExecutor(changesFn) + + // Craft the proposal comment + comment := ufmt.Sprintf( + "Proposal to add valoper %s (Address: %s; PubKey: %s) to the valset", + valoper.Name, + valoper.Address.String(), + valoper.PubKey, + ) + + // Create the govdao proposal + govdao.Propose(comment, executor) +} diff --git a/examples/gno.land/r/gnoland/valopers/valopers_test.gno b/examples/gno.land/r/gnoland/valopers/valopers_test.gno new file mode 100644 index 00000000000..89544c46ee5 --- /dev/null +++ b/examples/gno.land/r/gnoland/valopers/valopers_test.gno @@ -0,0 +1,149 @@ +package valopers + +import ( + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestValopers_Register(t *testing.T) { + t.Parallel() + + t.Run("already a valoper", func(t *testing.T) { + t.Parallel() + + // Clear the set for the test + valopers = avl.NewTree() + + v := Valoper{ + Address: testutils.TestAddress("valoper"), + } + + // Add the valoper + valopers.Set(v.Address.String(), v) + + uassert.PanicsWithMessage(t, errValoperExists, func() { + Register(v) + }) + }) + + t.Run("successful registration", func(t *testing.T) { + t.Parallel() + + // Clear the set for the test + valopers = avl.NewTree() + + v := Valoper{ + Address: testutils.TestAddress("valoper"), + Name: "new valoper", + PubKey: "pub key", + } + + uassert.NotPanics(t, func() { + Register(v) + }) + + uassert.NotPanics(t, func() { + valoper := GetByAddr(v.Address) + + uassert.Equal(t, v.Address, valoper.Address) + uassert.Equal(t, v.Name, valoper.Name) + uassert.Equal(t, v.PubKey, valoper.PubKey) + }) + }) +} + +func TestValopers_Update(t *testing.T) { + t.Parallel() + + t.Run("non-existing valoper", func(t *testing.T) { + t.Parallel() + + // Clear the set for the test + valopers = avl.NewTree() + + v := Valoper{} + + // Update the valoper + uassert.PanicsWithMessage(t, errValoperMissing, func() { + Update(v.Address, v) + }) + }) + + t.Run("overwrite valoper", func(t *testing.T) { + t.Parallel() + + // Clear the set for the test + valopers = avl.NewTree() + + one := Valoper{ + Address: testutils.TestAddress("valoper 1"), + } + + // Add the valoper + uassert.NotPanics(t, func() { + Register(one) + }) + + initialAddress := testutils.TestAddress("valoper 2") + two := Valoper{ + Address: initialAddress, + } + + // Add the valoper + uassert.NotPanics(t, func() { + Register(two) + }) + + // Update the valoper address + // so it overlaps + two = Valoper{ + Address: one.Address, + } + + // Update the valoper + uassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() { + Update(initialAddress, two) + }) + }) + + t.Run("successful update", func(t *testing.T) { + t.Parallel() + + // Clear the set for the test + valopers = avl.NewTree() + + var ( + name = "new valoper" + v = Valoper{ + Address: testutils.TestAddress("valoper"), + Name: name, + PubKey: "pub key", + } + ) + + // Add the valoper + uassert.NotPanics(t, func() { + Register(v) + }) + + // Update the valoper name + v.Name = "new name" + v.Active = false + + // Update the valoper + uassert.NotPanics(t, func() { + Update(v.Address, v) + }) + + // Make sure the valoper is updated + uassert.NotPanics(t, func() { + valoper := GetByAddr(v.Address) + + uassert.Equal(t, v.Name, valoper.Name) + uassert.Equal(t, v.Active, valoper.Active) + }) + }) +} diff --git a/examples/gno.land/r/gov/dao/dao.gno b/examples/gno.land/r/gov/dao/dao.gno new file mode 100644 index 00000000000..632935dafed --- /dev/null +++ b/examples/gno.land/r/gov/dao/dao.gno @@ -0,0 +1,207 @@ +package govdao + +import ( + "std" + "strconv" + + "gno.land/p/demo/ufmt" + pproposal "gno.land/p/gov/proposal" +) + +var ( + proposals = make([]*proposal, 0) + members = make([]std.Address, 0) // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs +) + +const ( + msgMissingExecutor = "missing proposal executor" + msgPropExecuted = "prop already executed" + msgPropExpired = "prop is expired" + msgPropInactive = "prop is not active anymore" + msgPropActive = "prop is still active" + msgPropNotAccepted = "prop is not accepted" + + msgCallerNotAMember = "caller is not member of govdao" + msgProposalNotFound = "proposal not found" +) + +type proposal struct { + author std.Address + comment string + executor pproposal.Executor + voter Voter + executed bool + voters []std.Address // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs. +} + +func (p proposal) Status() Status { + if p.executor.IsExpired() { + return Expired + } + + if p.executor.IsDone() { + return Succeeded + } + + if !p.voter.IsFinished(members) { + return Active + } + + if p.voter.IsAccepted(members) { + return Accepted + } + + return NotAccepted +} + +// Propose is designed to be called by another contract or with +// `maketx run`, not by a `maketx call`. +func Propose(comment string, executor pproposal.Executor) int { + // XXX: require payment? + if executor == nil { + panic(msgMissingExecutor) + } + caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE! + AssertIsMember(caller) + + prop := &proposal{ + comment: comment, + executor: executor, + author: caller, + voter: NewPercentageVoter(66), // at least 2/3 must say yes + } + + proposals = append(proposals, prop) + + return len(proposals) - 1 +} + +func VoteOnProposal(idx int, option string) { + assertProposalExists(idx) + caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE! + AssertIsMember(caller) + + prop := getProposal(idx) + + if prop.executed { + panic(msgPropExecuted) + } + + if prop.executor.IsExpired() { + panic(msgPropExpired) + } + + if prop.voter.IsFinished(members) { + panic(msgPropInactive) + } + + prop.voter.Vote(members, caller, option) +} + +func ExecuteProposal(idx int) { + assertProposalExists(idx) + prop := getProposal(idx) + + if prop.executed { + panic(msgPropExecuted) + } + + if prop.executor.IsExpired() { + panic(msgPropExpired) + } + + if !prop.voter.IsFinished(members) { + panic(msgPropActive) + } + + if !prop.voter.IsAccepted(members) { + panic(msgPropNotAccepted) + } + + prop.executor.Execute() + prop.voters = members + prop.executed = true +} + +func IsMember(addr std.Address) bool { + if len(members) == 0 { // special case for initial execution + return true + } + + for _, v := range members { + if v == addr { + return true + } + } + + return false +} + +func AssertIsMember(addr std.Address) { + if !IsMember(addr) { + panic(msgCallerNotAMember) + } +} + +func Render(path string) string { + if path == "" { + if len(proposals) == 0 { + return "No proposals found :(" // corner case + } + + output := "" + for idx, prop := range proposals { + output += ufmt.Sprintf("- [%d](/r/gov/dao:%d) - %s (**%s**)(by %s)\n", idx, idx, prop.comment, string(prop.Status()), prop.author) + } + + return output + } + + // else display the proposal + idx, err := strconv.Atoi(path) + if err != nil { + return "404" + } + + if !proposalExists(idx) { + return "404" + } + prop := getProposal(idx) + + vs := members + if prop.executed { + vs = prop.voters + } + + output := "" + output += ufmt.Sprintf("# Prop #%d", idx) + output += "\n\n" + output += prop.comment + output += "\n\n" + output += ufmt.Sprintf("Status: %s", string(prop.Status())) + output += "\n\n" + output += ufmt.Sprintf("Voting status: %s", prop.voter.Status(vs)) + output += "\n\n" + output += ufmt.Sprintf("Author: %s", string(prop.author)) + output += "\n\n" + + return output +} + +func getProposal(idx int) *proposal { + if idx > len(proposals)-1 { + panic(msgProposalNotFound) + } + + return proposals[idx] +} + +func proposalExists(idx int) bool { + return idx >= 0 && idx <= len(proposals) +} + +func assertProposalExists(idx int) { + if !proposalExists(idx) { + panic("invalid proposal id") + } +} diff --git a/examples/gno.land/r/gov/dao/dao_test.gno b/examples/gno.land/r/gov/dao/dao_test.gno new file mode 100644 index 00000000000..96eaba7f5e9 --- /dev/null +++ b/examples/gno.land/r/gov/dao/dao_test.gno @@ -0,0 +1,192 @@ +package govdao + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" + pproposal "gno.land/p/gov/proposal" +) + +func TestPackage(t *testing.T) { + u1 := testutils.TestAddress("u1") + u2 := testutils.TestAddress("u2") + u3 := testutils.TestAddress("u3") + + members = append(members, u1) + members = append(members, u2) + members = append(members, u3) + + nu1 := testutils.TestAddress("random1") + + out := Render("") + + expected := "No proposals found :(" + urequire.Equal(t, expected, out) + + var called bool + ex := pproposal.NewExecutor(func() error { + called = true + return nil + }) + + std.TestSetOrigCaller(u1) + pid := Propose("dummy proposal", ex) + + // try to vote not being a member + std.TestSetOrigCaller(nu1) + + urequire.PanicsWithMessage(t, msgCallerNotAMember, func() { + VoteOnProposal(pid, "YES") + }) + + // try to vote several times + std.TestSetOrigCaller(u1) + urequire.NotPanics(t, func() { + VoteOnProposal(pid, "YES") + }) + urequire.PanicsWithMessage(t, msgAlreadyVoted, func() { + VoteOnProposal(pid, "YES") + }) + + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: active + +Voting status: YES: 1, NO: 0, percent: 33, members: 3 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + + std.TestSetOrigCaller(u2) + urequire.PanicsWithMessage(t, msgWrongVotingValue, func() { + VoteOnProposal(pid, "INCORRECT") + }) + urequire.NotPanics(t, func() { + VoteOnProposal(pid, "NO") + }) + + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: active + +Voting status: YES: 1, NO: 1, percent: 33, members: 3 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + + std.TestSetOrigCaller(u3) + urequire.NotPanics(t, func() { + VoteOnProposal(pid, "YES") + }) + + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: accepted + +Voting status: YES: 2, NO: 1, percent: 66, members: 3 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + + // Add a new member, so non-executed proposals will change the voting status + u4 := testutils.TestAddress("u4") + members = append(members, u4) + + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: active + +Voting status: YES: 2, NO: 1, percent: 50, members: 4 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + + std.TestSetOrigCaller(u4) + urequire.NotPanics(t, func() { + VoteOnProposal(pid, "YES") + }) + + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: accepted + +Voting status: YES: 3, NO: 1, percent: 75, members: 4 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + + ExecuteProposal(pid) + urequire.True(t, called) + + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: succeeded + +Voting status: YES: 3, NO: 1, percent: 75, members: 4 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + + // Add a new member and try to vote an already executed proposal + u5 := testutils.TestAddress("u5") + members = append(members, u5) + std.TestSetOrigCaller(u5) + urequire.PanicsWithMessage(t, msgPropExecuted, func() { + ExecuteProposal(pid) + }) + + // even if we added a new member the executed proposal is showing correctly the members that voted on it + out = Render("0") + expected = `# Prop #0 + +dummy proposal + +Status: succeeded + +Voting status: YES: 3, NO: 1, percent: 75, members: 4 + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` + + urequire.Equal(t, expected, out) + +} diff --git a/examples/gno.land/r/gov/dao/gno.mod b/examples/gno.land/r/gov/dao/gno.mod new file mode 100644 index 00000000000..f3c0bae990e --- /dev/null +++ b/examples/gno.land/r/gov/dao/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gov/dao + +require ( + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest +) diff --git a/examples/gno.land/r/gov/dao/memberset.gno b/examples/gno.land/r/gov/dao/memberset.gno new file mode 100644 index 00000000000..3abd52ae99d --- /dev/null +++ b/examples/gno.land/r/gov/dao/memberset.gno @@ -0,0 +1,40 @@ +package govdao + +import ( + "std" + + pproposal "gno.land/p/gov/proposal" +) + +const daoPkgPath = "gno.land/r/gov/dao" + +const ( + errNoChangesProposed = "no set changes proposed" + errNotGovDAO = "caller not govdao executor" +) + +func NewPropExecutor(changesFn func() []std.Address) pproposal.Executor { + if changesFn == nil { + panic(errNoChangesProposed) + } + + callback := func() error { + // Make sure the GovDAO executor runs the valset changes + assertGovDAOCaller() + + for _, addr := range changesFn() { + members = append(members, addr) + } + + return nil + } + + return pproposal.NewExecutor(callback) +} + +// assertGovDAOCaller verifies the caller is the GovDAO executor +func assertGovDAOCaller() { + if std.CurrentRealm().PkgPath() != daoPkgPath { + panic(errNotGovDAO) + } +} diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno new file mode 100644 index 00000000000..49a200fd561 --- /dev/null +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -0,0 +1,131 @@ +// Please note that this package is intended for demonstration purposes only. +// You could execute this code (the init part) by running a `maketx run` command +// or by uploading a similar package to a personal namespace. +// +// For the specific case of validators, a `r/gnoland/valopers` will be used to +// organize the lifecycle of validators (register, etc), and this more complex +// contract will be responsible to generate proposals. +package main + +import ( + "std" + + pVals "gno.land/p/sys/validators" + govdao "gno.land/r/gov/dao" + "gno.land/r/sys/validators" +) + +const daoPkgPath = "gno.land/r/gov/dao" + +func init() { + membersFn := func() []std.Address { + return []std.Address{ + std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"), + } + } + + mExec := govdao.NewPropExecutor(membersFn) + + comment := "adding someone to vote" + id := govdao.Propose(comment, mExec) + govdao.ExecuteProposal(id) + + changesFn := func() []pVals.Validator { + return []pVals.Validator{ + { + Address: std.Address("g12345678"), + PubKey: "pubkey", + VotingPower: 10, // add a new validator + }, + { + Address: std.Address("g000000000"), + PubKey: "pubkey", + VotingPower: 10, // add a new validator + }, + { + Address: std.Address("g000000000"), + PubKey: "pubkey", + VotingPower: 0, // remove an existing validator + }, + } + } + + // Wraps changesFn to emit a certified event only if executed from a + // complete governance proposal process. + executor := validators.NewPropExecutor(changesFn) + + // Create a proposal. + // XXX: payment + comment = "manual valset changes proposal example" + govdao.Propose(comment, executor) +} + +func main() { + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("1")) + println("--") + govdao.VoteOnProposal(1, "YES") + println("--") + println(govdao.Render("1")) + println("--") + println(validators.Render("")) + println("--") + govdao.ExecuteProposal(1) + println("--") + println(govdao.Render("1")) + println("--") + println(validators.Render("")) +} + +// Output: +// -- +// - [0](/r/gov/dao:0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// - [1](/r/gov/dao:1) - manual valset changes proposal example (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// +// -- +// # Prop #1 +// +// manual valset changes proposal example +// +// Status: active +// +// Voting status: YES: 0, NO: 0, percent: 0, members: 1 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// +// -- +// -- +// # Prop #1 +// +// manual valset changes proposal example +// +// Status: accepted +// +// Voting status: YES: 1, NO: 0, percent: 100, members: 1 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// +// -- +// No valset changes to apply. +// -- +// -- +// # Prop #1 +// +// manual valset changes proposal example +// +// Status: succeeded +// +// Voting status: YES: 1, NO: 0, percent: 100, members: 1 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// +// -- +// Valset changes: +// - #123: g12345678 (10) +// - #123: g000000000 (10) +// - #123: g000000000 (0) diff --git a/examples/gno.land/r/gov/dao/prop2_filetest.gno b/examples/gno.land/r/gov/dao/prop2_filetest.gno new file mode 100644 index 00000000000..047709cc45f --- /dev/null +++ b/examples/gno.land/r/gov/dao/prop2_filetest.gno @@ -0,0 +1,120 @@ +package main + +import ( + "std" + "time" + + "gno.land/p/demo/context" + "gno.land/p/gov/proposal" + gnoblog "gno.land/r/gnoland/blog" + govdao "gno.land/r/gov/dao" +) + +func init() { + membersFn := func() []std.Address { + return []std.Address{ + std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"), + } + } + + mExec := govdao.NewPropExecutor(membersFn) + + comment := "adding someone to vote" + + id := govdao.Propose(comment, mExec) + + govdao.ExecuteProposal(id) + + executor := proposal.NewCtxExecutor(func(ctx context.Context) error { + gnoblog.DaoAddPost( + ctx, + "hello-from-govdao", // slug + "Hello from GovDAO!", // title + "This post was published by a GovDAO proposal.", // body + time.Now().Format(time.RFC3339), // publidation date + "moul", // authors + "govdao,example", // tags + ) + return nil + }) + + // Create a proposal. + // XXX: payment + comment = "post a new blogpost about govdao" + govdao.Propose(comment, executor) +} + +func main() { + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("1")) + println("--") + govdao.VoteOnProposal(1, "YES") + println("--") + println(govdao.Render("1")) + println("--") + println(gnoblog.Render("")) + println("--") + govdao.ExecuteProposal(1) + println("--") + println(govdao.Render("1")) + println("--") + println(gnoblog.Render("")) +} + +// Output: +// -- +// - [0](/r/gov/dao:0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// - [1](/r/gov/dao:1) - post a new blogpost about govdao (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// +// -- +// # Prop #1 +// +// post a new blogpost about govdao +// +// Status: active +// +// Voting status: YES: 0, NO: 0, percent: 0, members: 1 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// +// -- +// -- +// # Prop #1 +// +// post a new blogpost about govdao +// +// Status: accepted +// +// Voting status: YES: 1, NO: 0, percent: 100, members: 1 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// +// -- +// # Gnoland's Blog +// +// No posts. +// -- +// -- +// # Prop #1 +// +// post a new blogpost about govdao +// +// Status: succeeded +// +// Voting status: YES: 1, NO: 0, percent: 100, members: 1 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// +// -- +// # Gnoland's Blog +// +//
+// +// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao) +// 13 Feb 2009 +//
diff --git a/examples/gno.land/r/gov/dao/types.gno b/examples/gno.land/r/gov/dao/types.gno new file mode 100644 index 00000000000..123fc489075 --- /dev/null +++ b/examples/gno.land/r/gov/dao/types.gno @@ -0,0 +1,32 @@ +package govdao + +import ( + "std" +) + +// Status enum. +type Status string + +var ( + Accepted Status = "accepted" + Active Status = "active" + NotAccepted Status = "not accepted" + Expired Status = "expired" + Succeeded Status = "succeeded" +) + +// Voter defines the needed methods for a voting system +type Voter interface { + + // IsAccepted indicates if the voting process had been accepted + IsAccepted(voters []std.Address) bool + + // IsFinished indicates if the voting process is finished + IsFinished(voters []std.Address) bool + + // Vote adds a new vote to the voting system + Vote(voters []std.Address, caller std.Address, flag string) + + // Status returns a human friendly string describing how the voting process is going + Status(voters []std.Address) string +} diff --git a/examples/gno.land/r/gov/dao/voter.gno b/examples/gno.land/r/gov/dao/voter.gno new file mode 100644 index 00000000000..99223210791 --- /dev/null +++ b/examples/gno.land/r/gov/dao/voter.gno @@ -0,0 +1,91 @@ +package govdao + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +const ( + yay = "YES" + nay = "NO" + + msgNoMoreVotesAllowed = "no more votes allowed" + msgAlreadyVoted = "caller already voted" + msgWrongVotingValue = "voting values must be YES or NO" +) + +func NewPercentageVoter(percent int) *PercentageVoter { + if percent < 0 || percent > 100 { + panic("percent value must be between 0 and 100") + } + + return &PercentageVoter{ + percentage: percent, + } +} + +// PercentageVoter is a system based on the amount of received votes. +// When the specified treshold is reached, the voting process finishes. +type PercentageVoter struct { + percentage int + + voters []std.Address + yes int + no int +} + +func (pv *PercentageVoter) IsAccepted(voters []std.Address) bool { + if len(voters) == 0 { + return true // special case + } + + return pv.percent(voters) >= pv.percentage +} + +func (pv *PercentageVoter) IsFinished(voters []std.Address) bool { + return pv.yes+pv.no >= len(voters) +} + +func (pv *PercentageVoter) Status(voters []std.Address) string { + return ufmt.Sprintf("YES: %d, NO: %d, percent: %d, members: %d", pv.yes, pv.no, pv.percent(voters), len(voters)) +} + +func (pv *PercentageVoter) Vote(voters []std.Address, caller std.Address, flag string) { + if pv.IsFinished(voters) { + panic(msgNoMoreVotesAllowed) + } + + if pv.alreadyVoted(caller) { + panic(msgAlreadyVoted) + } + + switch flag { + case yay: + pv.yes++ + pv.voters = append(pv.voters, caller) + case nay: + pv.no++ + pv.voters = append(pv.voters, caller) + default: + panic(msgWrongVotingValue) + } +} + +func (pv *PercentageVoter) percent(voters []std.Address) int { + if len(voters) == 0 { + return 0 + } + + return int((float32(pv.yes) / float32(len(voters))) * 100) +} + +func (pv *PercentageVoter) alreadyVoted(addr std.Address) bool { + for _, v := range pv.voters { + if v == addr { + return true + } + } + + return false +} diff --git a/examples/gno.land/r/sys/names/genesis.gno b/examples/gno.land/r/sys/names/genesis.gno deleted file mode 100644 index 7e4ae51645f..00000000000 --- a/examples/gno.land/r/sys/names/genesis.gno +++ /dev/null @@ -1,30 +0,0 @@ -package names - -import "std" - -func init() { - // Please, do not edit this file to reserve your username, use a transaction instead. - var ( - jaekwon = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - manfred = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") - test1 = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - reservedAdmin = std.Address("g100000000000000000000000000000000000000") // FIXME: create a multisig. - reservedNames = []string{ - // FIXME: complete this list. - "gno", "gnolang", "tendermint", "cosmos", "hub", "admin", - "ethereum", "bitcoin", - // FIXME: reserve brands? then, require KYC to unlock? - } - ) - namespaces.Set("demo", &Space{Admins: []std.Address{jaekwon, manfred}}) - namespaces.Set("gnoland", &Space{Admins: []std.Address{jaekwon, manfred}}) - namespaces.Set("sys", &Space{Admins: []std.Address{jaekwon, manfred}}) - namespaces.Set("gov", &Space{Admins: []std.Address{jaekwon, manfred}}) - namespaces.Set("jaekwon", &Space{Admins: []std.Address{jaekwon}}) - namespaces.Set("manfred", &Space{Admins: []std.Address{manfred}}) - namespaces.Set("test1", &Space{Admins: []std.Address{test1}}) - - for _, keyword := range reservedNames { - namespaces.Set(keyword, &Space{Admins: []std.Address{reservedAdmin}}) - } -} diff --git a/examples/gno.land/r/sys/names/gno.mod b/examples/gno.land/r/sys/names/gno.mod deleted file mode 100644 index 97236a84892..00000000000 --- a/examples/gno.land/r/sys/names/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/sys/names - -require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/r/sys/names/names.gno b/examples/gno.land/r/sys/names/names.gno deleted file mode 100644 index d73ec59b2c5..00000000000 --- a/examples/gno.land/r/sys/names/names.gno +++ /dev/null @@ -1,60 +0,0 @@ -// The realm r/sys/names is used to manage namespaces on gno.land. -package names - -import ( - "std" - - "gno.land/p/demo/avl" -) - -// "AddPkg" will check if r/sys/names exists. If yes, it will -// inspect the realm's state and use the following variable to -// determine if an address can publish a package or not. -var namespaces avl.Tree // name(string) -> Space - -type Space struct { - Admins []std.Address - Editors []std.Address - InPause bool -} - -func Register(namespace string) { - // TODO: input sanitization: - // - already exists / reserved. - // - min/max length, format. - // - fees (dynamic, based on length). - panic("not implemented") -} - -func AddAdmin(namespace string, newAdmin std.Address) { - // TODO: assertIsAdmin() - panic("not implemented") -} - -func RemoveAdmin(namespace string, newAdmin std.Address) { - // TODO: assertIsAdmin() - // TODO: check if self. - panic("not implemented") -} - -func AddEditor(namespace string, newAdmin std.Address) { - // TODO: assertIsAdmin() - panic("not implemented") -} - -func RemoveEditor(namespace string, newAdmin std.Address) { - // TODO: assertIsAdmin() - // TODO: check if self. - panic("not implemented") -} - -func SetInPause(namespace string, state bool) { - // TODO: assertIsAdmin() - panic("not implemented") -} - -func Render(path string) string { - // TODO: by namespace. - // TODO: by address. - return "not implemented" -} diff --git a/examples/gno.land/r/sys/users/gno.mod b/examples/gno.land/r/sys/users/gno.mod new file mode 100644 index 00000000000..774a364a272 --- /dev/null +++ b/examples/gno.land/r/sys/users/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/users + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/users/verify.gno b/examples/gno.land/r/sys/users/verify.gno new file mode 100644 index 00000000000..852626622e4 --- /dev/null +++ b/examples/gno.land/r/sys/users/verify.gno @@ -0,0 +1,83 @@ +package users + +import ( + "std" + + "gno.land/p/demo/ownable" + "gno.land/r/demo/users" +) + +const admin = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" // @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 err := owner.CallerIsOwner(); err != nil { + panic(err) + } + + enabled = true +} + +// Disable this package. +func AdminDisable() { + if err := owner.CallerIsOwner(); err != nil { + panic(err) + } + + enabled = false +} + +// AdminUpdateVerifyCall updates the method that verifies the namespace. +func AdminUpdateVerifyCall(check VerifyNameFunc) { + if err := owner.CallerIsOwner(); err != nil { + panic(err) + } + + checkFunc = check +} + +// AdminTransferOwnership transfers the ownership to a new owner. +func AdminTransferOwnership(newOwner std.Address) error { + if err := owner.CallerIsOwner(); err != nil { + panic(err) + } + + return owner.TransferOwnership(newOwner) +} diff --git a/examples/gno.land/r/sys/validators/doc.gno b/examples/gno.land/r/sys/validators/doc.gno new file mode 100644 index 00000000000..d17b29ed110 --- /dev/null +++ b/examples/gno.land/r/sys/validators/doc.gno @@ -0,0 +1,3 @@ +// Package validators implements the on-chain validator set management through Proof of Contribution. +// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. +package validators diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/gno.mod index 84df66b9001..d9d129dd543 100644 --- a/examples/gno.land/r/sys/validators/gno.mod +++ b/examples/gno.land/r/sys/validators/gno.mod @@ -1 +1,12 @@ module gno.land/r/sys/validators + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest + gno.land/p/nt/poa v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno new file mode 100644 index 00000000000..0a85c0682b8 --- /dev/null +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -0,0 +1,24 @@ +package validators + +import ( + "gno.land/p/sys/validators" +) + +// GetChanges returns the validator changes stored on the realm, since the given block number. +// This function is intended to be called by gno.land through the GnoSDK +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 { + chs := value.([]change) + + for _, ch := range chs { + valsetChanges = append(valsetChanges, ch.validator) + } + + return false + }) + + return valsetChanges +} diff --git a/examples/gno.land/r/sys/validators/init.gno b/examples/gno.land/r/sys/validators/init.gno new file mode 100644 index 00000000000..0c2af983beb --- /dev/null +++ b/examples/gno.land/r/sys/validators/init.gno @@ -0,0 +1,14 @@ +package validators + +import ( + "gno.land/p/demo/avl" + "gno.land/p/nt/poa" +) + +func init() { + // The default valset protocol is PoA + vp = poa.NewPoA() + + // No changes to apply initially + changes = avl.NewTree() +} diff --git a/examples/gno.land/r/sys/validators/poc.gno b/examples/gno.land/r/sys/validators/poc.gno new file mode 100644 index 00000000000..e088b3b4293 --- /dev/null +++ b/examples/gno.land/r/sys/validators/poc.gno @@ -0,0 +1,66 @@ +package validators + +import ( + "std" + + "gno.land/p/gov/proposal" + "gno.land/p/sys/validators" +) + +const daoPkgPath = "gno.land/r/gov/dao" + +const ( + errNoChangesProposed = "no set changes proposed" + errNotGovDAO = "caller not govdao executor" +) + +// NewPropExecutor creates a new executor that wraps a changes closure +// proposal. This wrapper is required to ensure the GovDAO Realm actually +// executed the callback. +// +// Concept adapted from: +// https://github.com/gnolang/gno/pull/1945 +func NewPropExecutor(changesFn func() []validators.Validator) proposal.Executor { + if changesFn == nil { + panic(errNoChangesProposed) + } + + callback := func() error { + // Make sure the GovDAO executor runs the valset changes + assertGovDAOCaller() + + for _, change := range changesFn() { + if change.VotingPower == 0 { + // This change request is to remove the validator + removeValidator(change.Address) + + continue + } + + // This change request is to add the validator + addValidator(change) + } + + return nil + } + + return proposal.NewExecutor(callback) +} + +// assertGovDAOCaller verifies the caller is the GovDAO executor +func assertGovDAOCaller() { + if std.PrevRealm().PkgPath() != daoPkgPath { + panic(errNotGovDAO) + } +} + +// IsValidator returns a flag indicating if the given bech32 address +// is part of the validator set +func IsValidator(addr std.Address) bool { + return vp.IsValidator(addr) +} + +// GetValidators returns the typed validator set +func GetValidators() []validators.Validator { + return vp.GetValidators() +} diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index dc9d731f075..bf42ece4990 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -1,4 +1,117 @@ -// This package is used to manage the validator set. package validators -// write specs. +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/sys/validators" +) + +var ( + vp validators.ValsetProtocol // p is the underlying validator set protocol + changes *avl.Tree // changes holds any valset changes; seqid(block number) -> []change +) + +// change represents a single valset change, tied to a specific block number +type change struct { + blockNum int64 // the block number associated with the valset change + validator validators.Validator // the validator update +} + +// addValidator adds a new validator to the validator set. +// If the validator is already present, the method errors out +func addValidator(validator validators.Validator) { + val, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower) + if err != nil { + panic(err) + } + + // Validator added, note the change + ch := change{ + blockNum: std.GetHeight(), + validator: val, + } + + saveChange(ch) + + // Emit the validator set change + std.Emit(validators.ValidatorAddedEvent) +} + +// removeValidator removes the given validator from the set. +// If the validator is not present in the set, the method errors out +func removeValidator(address std.Address) { + val, err := vp.RemoveValidator(address) + if err != nil { + panic(err) + } + + // Validator removed, note the change + ch := change{ + blockNum: std.GetHeight(), + validator: validators.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: 0, // nullified the voting power indicates removal + }, + } + + saveChange(ch) + + // Emit the validator set change + std.Emit(validators.ValidatorRemovedEvent) +} + +// saveChange saves the valset change +func saveChange(ch change) { + id := getBlockID(ch.blockNum) + + setRaw, exists := changes.Get(id) + if !exists { + changes.Set(id, []change{ch}) + + return + } + + // Save the change + set := setRaw.([]change) + set = append(set, ch) + + changes.Set(id, set) +} + +// getBlockID converts the block number to a sequential ID +func getBlockID(blockNum int64) string { + return seqid.ID(uint64(blockNum)).String() +} + +func Render(_ string) string { + var ( + size = changes.Size() + maxDisplay = 10 + ) + + if size == 0 { + return "No valset changes to apply." + } + + output := "Valset changes:\n" + changes.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool { + chs := value.([]change) + + for _, ch := range chs { + output += ufmt.Sprintf( + "- #%d: %s (%d)\n", + ch.blockNum, + ch.validator.Address.String(), + ch.validator.VotingPower, + ) + } + + return false + }) + + return output +} diff --git a/examples/gno.land/r/sys/validators/validators_test.gno b/examples/gno.land/r/sys/validators/validators_test.gno new file mode 100644 index 00000000000..177d84144cb --- /dev/null +++ b/examples/gno.land/r/sys/validators/validators_test.gno @@ -0,0 +1,102 @@ +package validators + +import ( + "testing" + + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/sys/validators" +) + +// generateTestValidators generates a dummy validator set +func generateTestValidators(count int) []validators.Validator { + vals := make([]validators.Validator, 0, count) + + for i := 0; i < count; i++ { + val := validators.Validator{ + Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), + PubKey: "public-key", + VotingPower: 10, + } + + vals = append(vals, val) + } + + return vals +} + +func TestValidators_AddRemove(t *testing.T) { + // Clear any changes + changes = avl.NewTree() + + var ( + vals = generateTestValidators(100) + initialHeight = int64(123) + ) + + // Add in the validators + for _, val := range vals { + addValidator(val) + + // Make sure the validator is added + uassert.True(t, vp.IsValidator(val.Address)) + + std.TestSkipHeights(1) + } + + for i := initialHeight; i < initialHeight+int64(len(vals)); i++ { + // Make sure the changes are saved + chs := GetChanges(i) + + // We use the funky index calculation to make sure + // changes are properly handled for each block span + uassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs))) + + for index, val := range vals[i-initialHeight:] { + // Make sure the changes are equal to the additions + ch := chs[index] + + uassert.Equal(t, val.Address, ch.Address) + uassert.Equal(t, val.PubKey, ch.PubKey) + uassert.Equal(t, val.VotingPower, ch.VotingPower) + } + } + + // Save the beginning height for the removal + initialRemoveHeight := std.GetHeight() + + // Clear any changes + changes = avl.NewTree() + + // Remove the validators + for _, val := range vals { + removeValidator(val.Address) + + // Make sure the validator is removed + uassert.False(t, vp.IsValidator(val.Address)) + + std.TestSkipHeights(1) + } + + for i := initialRemoveHeight; i < initialRemoveHeight+int64(len(vals)); i++ { + // Make sure the changes are saved + chs := GetChanges(i) + + // We use the funky index calculation to make sure + // changes are properly handled for each block span + uassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs))) + + for index, val := range vals[i-initialRemoveHeight:] { + // Make sure the changes are equal to the additions + ch := chs[index] + + uassert.Equal(t, val.Address, ch.Address) + uassert.Equal(t, val.PubKey, ch.PubKey) + uassert.Equal(t, uint64(0), ch.VotingPower) + } + } +} diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index e6f705c46b9..7044f0f72b3 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -1,6 +1,5 @@ +// Draft + module gno.land/r/x/manfred_outfmt -require ( - gno.land/p/demo/rand v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno index 5468a65c06f..01981024189 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno @@ -2,9 +2,9 @@ package outfmt import ( "encoding/json" + "math/rand" "strings" - "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) @@ -27,22 +27,21 @@ func (res *Result) String() string { return output } -var rSeed int64 +var rSeed = rand.NewPCG(0, 0) func genResult() Result { - // init rand - r := rand.FromSeed(rSeed) - defer func() { rSeed = r.Seed() }() + r := rand.New(rSeed) + // init rand res := Result{ Text: "Hello Gnomes!", - Number: r.Intn(1000), + Number: r.IntN(1000), } - length := r.Intn(8) + 2 + length := r.IntN(8) + 2 res.Numbers = make([]int, length) for i := 0; i < length; i++ { - res.Numbers[i] = r.Intn(100) + res.Numbers[i] = r.IntN(100) } return res diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno index c0c14139d52..69c07bbbf16 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno @@ -15,7 +15,7 @@ func TestRender(t *testing.T) { * [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp) ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -23,11 +23,11 @@ func TestRender(t *testing.T) { { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 957 -Numbers: 3 54 32 88 +Number: 222 +Numbers: 34 44 39 7 72 48 74 ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -35,29 +35,30 @@ Numbers: 3 54 32 88 { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 141 -Numbers: 98 27 +Number: 898 +Numbers: 24 25 2 ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } // json { + got := outfmt.Render("?fmt=json") - expected := `{"Number":801,"Text":"Hello Gnomes!","Numbers":[5,78,51,78,91,41]}` + expected := `{"Number":746,"Text":"Hello Gnomes!","Numbers":[57,82,16,14,28,32]}` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } // jsonp { got := outfmt.Render("?fmt=jsonp") - expected := `callback({"Number":63,"Text":"Hello Gnomes!","Numbers":[2,66,50,73,81]})` + expected := `callback({"Number":795,"Text":"Hello Gnomes!","Numbers":[29,51,88,61,93,21,2,66,79]})` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/README.md b/examples/gno.land/r/x/manfred_upgrade_patterns/README.md index 4b85cf6b230..8af19beb273 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/README.md +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/README.md @@ -1 +1,40 @@ -Various upgrade pattern explorations. +# Various upgrade pattern explorations + +This repository explores different upgrade patterns for Gno smart contracts. + +## `upgrade_a` + +- Versions are independent. +- Versions are not pausable; users can interact with them independently. +- New versions wrap the previous one (can be recursive) to extend the logic and optionally the storage. +- There is no consistency between versions; updating a version will impact the more recent ones but won't affect the older ones. +- Users and contracts interacting with non-latest versions won't have the latest state. + +## `upgrade_b` + +- Versions include a `SetNextVersion` function which pauses the current implementation and invites users interacting with a deprecated version to switch to the most recent one. +- Since only one version can be used at a time, the latest version can safely recycle the previous version's state in read-only mode. +- These logics can be applied recursively. +- Users and contracts interacting with non-latest versions will switch to a more restricted version (read-only). + +## `upgrade_c` + +- `root` is the storage contract with simple logic. +- Versions implement the logic and rely on `root` to manage the state. +- In the current example, only one version can write to `root` (the latest); in practice, it could be possible to support various logics concurrently relying on `root` for storage. + +## `upgrade_d` -- "Lazy Migration" + +- Demonstrates lazy migrations from v1 to v2 of a data structure in Gno. +- Uses AVL trees, but storage can vary since public `Get` functions are used. +- v1 can be made pausable and read-only during migration. + +## `upgrade_e` + +- `home` is the front-facing contract, focusing on exposing a consistent API to users. +- Versions implement an interface that `home` looks for and self-register themselves, which instantly makes `home` use the new logic implementation for ongoing calls. + +## `upgrade_f` + +- Similar to `upgrade_e`. +- Replaces self-registration with manual registration by an admin. diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_a/integration_test.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_a/integration_filetest.gno similarity index 97% rename from examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_a/integration_test.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_a/integration_filetest.gno index 491bc6575bf..31130ce8282 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_a/integration_test.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_a/integration_filetest.gno @@ -1,4 +1,4 @@ -package upgradea +package main import ( v1 "gno.land/r/x/manfred_upgrade_patterns/upgrade_a/v1" diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/integration_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/integration_filetest.gno new file mode 100644 index 00000000000..54bd32c194a --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/integration_filetest.gno @@ -0,0 +1,53 @@ +package main + +import ( + "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root" + "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1" + "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2" +) + +func main() { + println("# v1 impl") + println("root.Get()", root.Get()) + println("v1.Get()", v1.Get()) + println("v1.Inc()", v1.Inc()) + println("v1.Inc()", v1.Inc()) + println("v1.Inc()", v1.Inc()) + println("v1.Get()", v1.Get()) + println() + + println("# v2 impl") + root.SetCurrentImpl("gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2") + println("v2.Get()", v2.Get()) + println("v2.Inc()", v2.Inc()) + println("v2.Inc()", v2.Inc()) + println("v2.Inc()", v2.Inc()) + println("v2.Get()", v2.Get()) + println() + + println("# getters") + println("root.Get()", root.Get()) + println("v1.Get()", v1.Get()) + println("v2.Get()", v2.Get()) +} + +// Output: +// # v1 impl +// root.Get() 0 +// v1.Get() 0 +// v1.Inc() 1 +// v1.Inc() 2 +// v1.Inc() 3 +// v1.Get() 3 +// +// # v2 impl +// v2.Get() 6 +// v2.Inc() 1003 +// v2.Inc() 2003 +// v2.Inc() 3003 +// v2.Get() 6006 +// +// # getters +// root.Get() 3003 +// v1.Get() 3003 +// v2.Get() 6006 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 926b347c1bf..0a610b0b196 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 @@ -1,13 +1,15 @@ package root +import "std" + var ( - counter int - currentImplementation = "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1" + counter int + currentImpl = "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1" ) -func Inc() int { - // TODO: if caller is currentImplementation - counter++ +func Inc(nb int) int { + assertIsCurrentImpl() + counter += nb return counter } @@ -15,7 +17,17 @@ func Get() int { return counter } -func UpdateCurrentImplementation(address string) { - // TODO: if is admin - currentImplementation = address +func SetCurrentImpl(pkgpath string) { + assertIsAdmin() + currentImpl = pkgpath +} + +func assertIsCurrentImpl() { + if std.PrevRealm().PkgPath() != currentImpl { + panic("unauthorized") + } +} + +func assertIsAdmin() { + // TODO } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1/v1.gno index 498217dfba0..d994f36a277 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1/v1.gno @@ -2,8 +2,8 @@ package v1 import "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root" -func Inc() { - root.Inc() +func Inc() int { + return root.Inc(1) } func Get() int { diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno index 7d07337a17d..11e6c4e5602 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno @@ -1,9 +1,9 @@ -package v1 +package v2 import "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root" -func Inc() { - root.Inc() +func Inc() int { + return root.Inc(1000) } func Get() int { diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1/v1.gno new file mode 100644 index 00000000000..3a2b1388fda --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1/v1.gno @@ -0,0 +1,34 @@ +package v1 + +import ( + "strconv" + + "gno.land/p/demo/avl" +) + +var myTree avl.Tree + +type MyStruct struct { + FieldA string + FieldB int +} + +func (s *MyStruct) String() string { + if s == nil { + return "nil" + } + return "v1:" + s.FieldA + ":" + strconv.Itoa(s.FieldB) +} + +func Get(key string) *MyStruct { + ret, ok := myTree.Get(key) + if !ok { + return nil + } + return ret.(*MyStruct) +} + +func init() { + myTree.Set("a", &MyStruct{FieldA: "a", FieldB: 1}) + myTree.Set("b", &MyStruct{FieldA: "b", FieldB: 2}) +} diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1/z_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1/z_filetest.gno new file mode 100644 index 00000000000..3f7213eadf5 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1/z_filetest.gno @@ -0,0 +1,16 @@ +package main + +import ( + "gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1" +) + +func main() { + println("a", v1.Get("a")) + println("b", v1.Get("b")) + println("c", v1.Get("c")) +} + +// Output: +// a v1:a:1 +// b v1:b:2 +// c nil diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno new file mode 100644 index 00000000000..22e43cf7bdd --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno @@ -0,0 +1,51 @@ +package v2 + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" + "gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v1" +) + +var myTree avl.Tree + +// MyStruct represents the structure with fields that may have been migrated from v1. +type MyStruct struct { + FieldA string // Appending "imported-from-v1" if migrating from v1. + FieldB uint64 // Switching from int to uint64 and multiplying by 1000 if migrating from v1. + FieldC bool // Adding a boolean field which is true by default for v1 objects. +} + +// String returns a string representation of MyStruct. +func (s *MyStruct) String() string { + if s == nil { + return "nil" + } + return ufmt.Sprintf("v2:%s:%d:%t", s.FieldA, s.FieldB, s.FieldC) +} + +// Get retrieves a MyStruct from the tree by key. If the key does not exist in the current version, +// it attempts to retrieve and migrate the structure from v1. +func Get(key string) *MyStruct { + ret, ok := myTree.Get(key) + if !ok { + v1Struct := v1.Get(key) + if v1Struct == nil { + return nil + } + // Lazy migration code: convert v1 structure to v2 structure. + v2Struct := &MyStruct{ + FieldA: v1Struct.FieldA + "-imported-from-v1", + FieldB: uint64(v1Struct.FieldB * 1000), + FieldC: true, + } + myTree.Set(key, v2Struct) + return v2Struct + } + return ret.(*MyStruct) +} + +// init initializes the tree with some default values. +func init() { + myTree.Set("c", &MyStruct{FieldA: "c", FieldB: 3, FieldC: true}) + myTree.Set("d", &MyStruct{FieldA: "d", FieldB: 4, FieldC: false}) +} diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno new file mode 100644 index 00000000000..6fec97f49a2 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno @@ -0,0 +1,18 @@ +package main + +import "gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2" + +func main() { + println("a", v2.Get("a")) + println("b", v2.Get("b")) + println("c", v2.Get("c")) + println("d", v2.Get("d")) + println("e", v2.Get("e")) +} + +// Output: +// a v2:a-imported-from-v1:1000:true +// b v2:b-imported-from-v1:2000:true +// c v2:c:3:true +// d v2:d:4:false +// e nil diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/home.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/home.gno new file mode 100644 index 00000000000..418bc59236a --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/home.gno @@ -0,0 +1,38 @@ +package home + +import "gno.land/p/demo/nestedpkg" + +type myInterface interface { + Render(path string) string + Foo() error +} + +var currentImpl myInterface + +func SetImpl(impl myInterface) { + nestedpkg.AssertCallerIsSubPath() + currentImpl = impl +} + +func Render(path string) string { + assertImplIsDefined() + return currentImpl.Render(path) +} + +func Foo() error { + assertImplIsDefined() + return currentImpl.Foo() +} + +func Bar() error { + // doing some extra logic here + err := currentImpl.Foo() + // doing some more extra logic here + return err +} + +func assertImplIsDefined() { + if currentImpl == nil { + panic("no implementation") + } +} diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl/v1impl.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl/v1impl.gno new file mode 100644 index 00000000000..7ca2b1d7900 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl/v1impl.gno @@ -0,0 +1,19 @@ +package v1impl + +import ( + "errors" + + home "gno.land/r/x/manfred_upgrade_patterns/upgrade_e" +) + +// init is for self-registration, but in practice, anything can register like a `maketx run` call by an admin. +func init() { + // self register on init + impl := &Impl{} + home.SetImpl(impl) +} + +type Impl struct{} + +func (i Impl) Render(path string) string { return "hello from v1" } +func (i Impl) Foo() error { return errors.New("not implemented") } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl/z_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl/z_filetest.gno new file mode 100644 index 00000000000..cd93c44a7ac --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl/z_filetest.gno @@ -0,0 +1,15 @@ +package main + +import ( + home "gno.land/r/x/manfred_upgrade_patterns/upgrade_e" + _ "gno.land/r/x/manfred_upgrade_patterns/upgrade_e/v1impl" +) + +func main() { + println(home.Render("")) + println(home.Foo()) +} + +// Output: +// hello from v1 +// not implemented diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/home/home.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/home/home.gno new file mode 100644 index 00000000000..bee0fadb3b2 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/home/home.gno @@ -0,0 +1,40 @@ +package home + +type myInterface interface { + Render(path string) string + Foo() error +} + +var currentImpl myInterface + +func SetImpl(impl myInterface) { + assertIsAdmin() + currentImpl = impl +} + +func Render(path string) string { + assertImplIsDefined() + return currentImpl.Render(path) +} + +func Foo() error { + assertImplIsDefined() + return currentImpl.Foo() +} + +func Bar() error { + // doing some extra logic here + err := currentImpl.Foo() + // doing some more extra logic here + return err +} + +func assertImplIsDefined() { + if currentImpl == nil { + panic("no implementation") + } +} + +func assertIsAdmin() { + // TODO: unsafe +} diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl/v1impl.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl/v1impl.gno new file mode 100644 index 00000000000..2a1dc0715c0 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl/v1impl.gno @@ -0,0 +1,14 @@ +package v1impl + +import "errors" + +var impl = &Impl{} + +func Instance() *Impl { + return impl +} + +type Impl struct{} + +func (i Impl) Render(path string) string { return "hello from v1" } +func (i Impl) Foo() error { return errors.New("not implemented") } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl/z_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl/z_filetest.gno new file mode 100644 index 00000000000..685625d92a3 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl/z_filetest.gno @@ -0,0 +1,16 @@ +package main + +import ( + "gno.land/r/x/manfred_upgrade_patterns/upgrade_f/home" + "gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl" +) + +func main() { + home.SetImpl(v1impl.Instance()) + println(home.Render("")) + println(home.Foo()) +} + +// Output: +// hello from v1 +// not implemented diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl/v2impl.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl/v2impl.gno new file mode 100644 index 00000000000..66864392715 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl/v2impl.gno @@ -0,0 +1,12 @@ +package v2impl + +var impl = &Impl{} + +func Instance() *Impl { + return impl +} + +type Impl struct{} + +func (i Impl) Render(path string) string { return "hello from v2" } +func (i Impl) Foo() error { return nil } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl/z_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl/z_filetest.gno new file mode 100644 index 00000000000..5f7d78c39a3 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl/z_filetest.gno @@ -0,0 +1,25 @@ +package main + +import ( + "gno.land/r/x/manfred_upgrade_patterns/upgrade_f/home" + "gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v1impl" + "gno.land/r/x/manfred_upgrade_patterns/upgrade_f/v2impl" +) + +func main() { + home.SetImpl(v1impl.Instance()) + println(home.Render("")) + println(home.Foo()) + + println("-------------") + home.SetImpl(v2impl.Instance()) + println(home.Render("")) + println(home.Foo()) +} + +// Output: +// hello from v1 +// not implemented +// ------------- +// hello from v2 +// undefined diff --git a/examples/gno.land/r/x/map_delete/map_delete_test.gno b/examples/gno.land/r/x/map_delete/map_delete_test.gno index a0118befed2..c71fd4d2933 100644 --- a/examples/gno.land/r/x/map_delete/map_delete_test.gno +++ b/examples/gno.land/r/x/map_delete/map_delete_test.gno @@ -1,8 +1,6 @@ package mapdelete -import ( - "testing" -) +import "testing" func TestGetMap(t *testing.T) { if !(GetMap(3)) { diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/evaluation_test.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/evaluation_test.gno index b96f2022650..f9e0913010d 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/evaluation_test.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/evaluation_test.gno @@ -8,10 +8,8 @@ package evaluation */ import ( - "std" "testing" - "gno.land/p/demo/avl" "gno.land/p/demo/testutils" "gno.land/p/demo/ufmt" ) diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/tally.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/tally.gno index e02fba74341..30e88203f08 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/tally.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/tally.gno @@ -1,8 +1,6 @@ package evaluation -import ( - "gno.land/p/demo/avl" -) +import "gno.land/p/demo/avl" type TallyResult struct { results avl.Tree diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/vote.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/vote.gno index aad30cb1de4..f46fc18823b 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/vote.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/vote.gno @@ -1,8 +1,6 @@ package evaluation -import ( - "std" -) +import "std" const ( VoteYes = "YES" diff --git a/examples/gno.land/r/x/skip_height_to_skip_time/skiptime.gno b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime.gno new file mode 100644 index 00000000000..524e6f58ad9 --- /dev/null +++ b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime.gno @@ -0,0 +1 @@ +package skiptime 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 new file mode 100644 index 00000000000..52670a5626b --- /dev/null +++ b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno @@ -0,0 +1,27 @@ +package skiptime + +import ( + "std" + "testing" + "time" +) + +func TestSkipHeights(t *testing.T) { + oldHeight := std.GetHeight() + shouldEQ(t, oldHeight, 123) + + oldNow := time.Now().Unix() + shouldEQ(t, oldNow, 1234567890) + + // skip 3 blocks == 15 seconds + std.TestSkipHeights(3) + + shouldEQ(t, std.GetHeight()-oldHeight, 3) + shouldEQ(t, time.Now().Unix()-oldNow, 15) +} + +func shouldEQ(t *testing.T, got, expected int64) { + if got != expected { + t.Fatalf("expected %d, got %d.", expected, got) + } +} diff --git a/examples/gno.land/r/x/test_prev/gno.mod b/examples/gno.land/r/x/test_prev/gno.mod deleted file mode 100644 index a1c1eb4f4d5..00000000000 --- a/examples/gno.land/r/x/test_prev/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/x/test_prev - -require ( - gno.land/p/demo/users v0.0.0-latest - gno.land/r/demo/foo20 v0.0.0-latest -) diff --git a/examples/gno.land/r/x/test_prev/test_prev.gno b/examples/gno.land/r/x/test_prev/test_prev.gno deleted file mode 100644 index 9466e5f96b9..00000000000 --- a/examples/gno.land/r/x/test_prev/test_prev.gno +++ /dev/null @@ -1,18 +0,0 @@ -package test_prev - -import ( - "std" - - pusers "gno.land/p/demo/users" - "gno.land/r/demo/foo20" -) - -func DoSomeWithUserBalance() string { - caller := std.GetOrigCaller() - balance := foo20.BalanceOf(pusers.AddressOrName(caller)) - - if balance > 100 { - return "rich user" - } - return "poor user" -} diff --git a/examples/gno.land/r/x/test_prev/test_prev_test.gno b/examples/gno.land/r/x/test_prev/test_prev_test.gno deleted file mode 100644 index a6b86eafcb6..00000000000 --- a/examples/gno.land/r/x/test_prev/test_prev_test.gno +++ /dev/null @@ -1,32 +0,0 @@ -package test_prev - -import ( - "std" - "testing" - - "gno.land/r/demo/foo20" -) - -func TestDoSomeWithUserBalancePoor(t *testing.T) { - std.TestSetOrigCaller("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - if DoSomeWithUserBalance() != "poor user" { - t.Error("expected poor user") - } -} - -func TestDoSomeWithUserBalanceRichButPoor(t *testing.T) { - std.TestSetOrigCaller("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - foo20.Faucet() // foo20 will mint tokens to this realm(std.PrevRealm) not user - if DoSomeWithUserBalance() != "poor user" { - t.Error("expected poor user") - } -} - -func TestDoSomeWithUserBalanceRich(t *testing.T) { - std.TestSetPrevAddr("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - - foo20.Faucet() // foo20 will mint tokens to this realm not user - if DoSomeWithUserBalance() != "rich user" { - t.Error("expected rich user") - } -} diff --git a/gno.land/Makefile b/gno.land/Makefile index 88fe3b396c0..7b2afd5779f 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -25,7 +25,7 @@ GOTEST_FLAGS ?= -v -p 1 -timeout=30m ######################################## # Dev tools .PHONY: start.gnoland -start.gnoland:; go run ./cmd/gnoland start +start.gnoland:; go run ./cmd/gnoland start -lazy .PHONY: start.gnoweb start.gnoweb:; go run ./cmd/gnoweb diff --git a/gno.land/README.md b/gno.land/README.md index 5fdd95a84ad..7da2a8574de 100644 --- a/gno.land/README.md +++ b/gno.land/README.md @@ -4,7 +4,7 @@ Gno.land is a layer-1 blockchain that integrates various cutting-edge technologi ## Getting started -Use [`gnokey`](./cmd/gnokey) to interact with Gno.land's testnets, localnet, and upcoming mainnet. +Use [`gnokey`](./cmd/gnokey) to interact with gno.land's testnets, localnet, and upcoming mainnet. For localnet setup, use [`gnoland`](./cmd/gnoland). @@ -14,7 +14,7 @@ To add a web interface and faucet to your localnet, use [`gnoweb`](./cmd/gnoweb) Gno.land aims to offer security, high-quality contract libraries, and scalability to other Gnolang chains, while also prioritizing interoperability with existing and emerging chains. -Post mainnet launch, Gno.land aims to integrate IBCv1 to connect with existing Cosmos chains and implement ICS1 for security through the existing chains. +Post mainnet launch, gno.land aims to integrate IBCv1 to connect with existing Cosmos chains and implement ICS1 for security through the existing chains. Afterwards, the platform plans to improve IBC by adding new capabilities for interchain smart-contracts. ## Under the hood diff --git a/gno.land/cmd/gnoland/config.go b/gno.land/cmd/gnoland/config.go index 22d23e3ecb1..fca72add21e 100644 --- a/gno.land/cmd/gnoland/config.go +++ b/gno.land/cmd/gnoland/config.go @@ -1,14 +1,19 @@ package main import ( + "encoding/json" "flag" "fmt" + "path/filepath" "reflect" "strings" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/commands" ) +const tryConfigInit = "unable to load config; try running `gnoland config init` or use the -lazy flag" + type configCfg struct { configPath string } @@ -39,11 +44,77 @@ func (c *configCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.configPath, "config-path", - "./config.toml", + constructConfigPath(defaultNodeDir), "the path for the config.toml", ) } +// constructConfigPath constructs the default config path, using +// the given node directory +func constructConfigPath(nodeDir string) string { + return filepath.Join( + nodeDir, + config.DefaultConfigDir, + config.DefaultConfigFileName, + ) +} + +// printKeyValue searches and prints the given key value in JSON +func printKeyValue[T *secrets | *config.Config]( + input T, + raw bool, + io commands.IO, + key ...string, +) error { + // prepareOutput prepares the JSON output, taking into account raw mode + prepareOutput := func(input any) (string, error) { + encoded, err := json.MarshalIndent(input, "", " ") + if err != nil { + return "", fmt.Errorf("unable to marshal JSON, %w", err) + } + + output := string(encoded) + + if raw { + if err := json.Unmarshal(encoded, &output); err != nil { + return "", fmt.Errorf("unable to unmarshal raw JSON, %w", err) + } + } + + return output, nil + } + + if len(key) == 0 { + // Print the entire input + output, err := prepareOutput(input) + if err != nil { + return err + } + + io.Println(output) + + return nil + } + + // Get the value using reflect + secretValue := reflect.ValueOf(input).Elem() + + // Get the value path, with sections separated out by a period + field, err := getFieldAtPath(secretValue, strings.Split(key[0], ".")) + if err != nil { + return err + } + + output, err := prepareOutput(field.Interface()) + if err != nil { + return err + } + + io.Println(output) + + return nil +} + // getFieldAtPath fetches the given field from the given path func getFieldAtPath(currentValue reflect.Value, path []string) (*reflect.Value, error) { // Look at the current section, and figure out if diff --git a/gno.land/cmd/gnoland/config_get.go b/gno.land/cmd/gnoland/config_get.go index ffcb739be9c..1fd4027ec60 100644 --- a/gno.land/cmd/gnoland/config_get.go +++ b/gno.land/cmd/gnoland/config_get.go @@ -3,9 +3,8 @@ package main import ( "context" "errors" + "flag" "fmt" - "reflect" - "strings" "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/commands" @@ -13,14 +12,20 @@ import ( var errInvalidConfigGetArgs = errors.New("invalid number of config get arguments provided") +type configGetCfg struct { + configCfg + + raw bool +} + // newConfigGetCmd creates the config get command func newConfigGetCmd(io commands.IO) *commands.Command { - cfg := &configCfg{} + cfg := &configGetCfg{} cmd := commands.NewCommand( commands.Metadata{ Name: "get", - ShortUsage: "config get ", + ShortUsage: "config get [flags] []", ShortHelp: "shows the Gno node configuration", LongHelp: "Shows the Gno node configuration at the given path " + "by fetching the option specified at ", @@ -34,40 +39,33 @@ func newConfigGetCmd(io commands.IO) *commands.Command { return cmd } -func execConfigGet(cfg *configCfg, io commands.IO, args []string) error { +func (c *configGetCfg) RegisterFlags(fs *flag.FlagSet) { + c.configCfg.RegisterFlags(fs) + + fs.BoolVar( + &c.raw, + "raw", + false, + "output raw string values, rather than as JSON strings", + ) +} + +func execConfigGet(cfg *configGetCfg, io commands.IO, args []string) error { // Load the config loadedCfg, err := config.LoadConfigFile(cfg.configPath) if err != nil { - return fmt.Errorf("unable to load config, %w", err) + return fmt.Errorf("%s, %w", tryConfigInit, err) } - // Make sure the edit arguments are valid - if len(args) != 1 { + // Make sure the get arguments are valid + if len(args) > 1 { return errInvalidConfigGetArgs } // Find and print the config field, if any - if err := printConfigField(loadedCfg, args[0], io); err != nil { - return fmt.Errorf("unable to update config field, %w", err) - } - - return nil -} - -// printConfigField prints the value of the field at the given path -func printConfigField(config *config.Config, key string, io commands.IO) error { - // Get the config value using reflect - configValue := reflect.ValueOf(config).Elem() - - // Get the value path, with sections separated out by a period - path := strings.Split(key, ".") - - field, err := getFieldAtPath(configValue, path) - if err != nil { - return err + if err := printKeyValue(loadedCfg, cfg.raw, io, args...); err != nil { + return fmt.Errorf("unable to get config field, %w", err) } - io.Printf("%v", field.Interface()) - return nil } diff --git a/gno.land/cmd/gnoland/config_get_test.go b/gno.land/cmd/gnoland/config_get_test.go index e8c27045205..f2ddc5ca6d0 100644 --- a/gno.land/cmd/gnoland/config_get_test.go +++ b/gno.land/cmd/gnoland/config_get_test.go @@ -3,11 +3,13 @@ package main import ( "bytes" "context" - "fmt" - "strconv" + "encoding/json" + "strings" "testing" + "time" "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,14 +29,15 @@ func TestConfig_Get_Invalid(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, "unable to load config") + assert.ErrorContains(t, cmdErr, tryConfigInit) } // testSetCase outlines the single test case for config get type testGetCase struct { name string field string - verifyFn func(*config.Config, string) + verifyFn func(*config.Config, []byte) + isRaw bool } // verifyGetTestTableCommon is the common test table @@ -57,6 +60,10 @@ func verifyGetTestTableCommon(t *testing.T, testTable []testGetCase) { path, } + if testCase.isRaw { + args = append(args, "--raw") + } + // Create the command IO mockOut := new(bytes.Buffer) @@ -75,11 +82,25 @@ func verifyGetTestTableCommon(t *testing.T, testTable []testGetCase) { loadedCfg, err := config.LoadConfigFile(path) require.NoError(t, err) - testCase.verifyFn(loadedCfg, mockOut.String()) + testCase.verifyFn(loadedCfg, mockOut.Bytes()) }) } } +func unmarshalJSONCommon[T any](t *testing.T, input []byte) T { + t.Helper() + + var output T + + require.NoError(t, json.Unmarshal(input, &output)) + + return output +} + +func escapeNewline(value []byte) string { + return strings.ReplaceAll(string(value), "\n", "") +} + func TestConfig_Get_Base(t *testing.T) { t.Parallel() @@ -87,99 +108,194 @@ func TestConfig_Get_Base(t *testing.T) { { "root dir fetched", "home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.RootDir, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RootDir, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "root dir fetched, raw", + "home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RootDir, escapeNewline(value)) }, + true, }, { "proxy app fetched", "proxy_app", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.ProxyApp, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProxyApp, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "proxy app fetched, raw", + "proxy_app", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProxyApp, escapeNewline(value)) + }, + true, }, { "moniker fetched", "moniker", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.Moniker, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Moniker, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "moniker fetched, raw", + "moniker", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Moniker, escapeNewline(value)) }, + true, }, { "fast sync mode fetched", "fast_sync", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, loadedCfg.FastSyncMode, boolVal) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.FastSyncMode, unmarshalJSONCommon[bool](t, value)) }, + false, }, { "db backend fetched", "db_backend", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.DBBackend, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBBackend, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "db backend fetched, raw", + "db_backend", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBBackend, escapeNewline(value)) }, + true, }, { "db path fetched", "db_dir", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.DBPath, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBPath, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "db path fetched, raw", + "db_dir", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBPath, escapeNewline(value)) }, + true, }, { "validator key fetched", "priv_validator_key_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.PrivValidatorKey, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorKey, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "validator key fetched, raw", + "priv_validator_key_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorKey, escapeNewline(value)) }, + true, }, { "validator state file fetched", "priv_validator_state_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.PrivValidatorState, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorState, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "validator state file fetched, raw", + "priv_validator_state_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorState, escapeNewline(value)) + }, + true, }, { "validator listen addr fetched", "priv_validator_laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.PrivValidatorListenAddr, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorListenAddr, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "validator listen addr fetched, raw", + "priv_validator_laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorListenAddr, escapeNewline(value)) + }, + true, }, { "node key path fetched", "node_key_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.NodeKey, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.NodeKey, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "node key path fetched, raw", + "node_key_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.NodeKey, escapeNewline(value)) }, + true, }, { "abci fetched", "abci", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.ABCI, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ABCI, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "abci fetched, raw", + "abci", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ABCI, escapeNewline(value)) + }, + true, }, { "profiling listen address fetched", "prof_laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.ProfListenAddress, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProfListenAddress, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "profiling listen address fetched, raw", + "prof_laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProfListenAddress, escapeNewline(value)) }, + true, }, { "filter peers flag fetched", "filter_peers", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, loadedCfg.FilterPeers, boolVal) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.FilterPeers, unmarshalJSONCommon[bool](t, value)) }, + false, }, } @@ -193,105 +309,178 @@ func TestConfig_Get_Consensus(t *testing.T) { { "root dir updated", "consensus.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.RootDir, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "root dir updated, raw", + "consensus.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.RootDir, escapeNewline(value)) }, + true, }, { "WAL path updated", "consensus.wal_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.WALPath) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.WALPath, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "WAL path updated, raw", + "consensus.wal_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.WALPath, escapeNewline(value)) }, + true, }, { "propose timeout updated", "consensus.timeout_propose", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPropose.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPropose, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "propose timeout delta updated", "consensus.timeout_propose_delta", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutProposeDelta.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutProposeDelta, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "prevote timeout updated", "consensus.timeout_prevote", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrevote.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrevote, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "prevote timeout delta updated", "consensus.timeout_prevote_delta", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrevoteDelta.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrevoteDelta, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "precommit timeout updated", "consensus.timeout_precommit", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrecommit.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrecommit, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "precommit timeout delta updated", "consensus.timeout_precommit_delta", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrecommitDelta.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrecommitDelta, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "commit timeout updated", "consensus.timeout_commit", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutCommit.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutCommit, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "skip commit timeout toggle updated", "consensus.skip_timeout_commit", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.Consensus.SkipTimeoutCommit) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.SkipTimeoutCommit, + unmarshalJSONCommon[bool](t, value), + ) }, + false, }, { "create empty blocks toggle updated", "consensus.create_empty_blocks", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - assert.Equal(t, boolVal, loadedCfg.Consensus.CreateEmptyBlocks) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.CreateEmptyBlocks, + unmarshalJSONCommon[bool](t, value), + ) }, + false, }, { "create empty blocks interval updated", "consensus.create_empty_blocks_interval", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.CreateEmptyBlocksInterval.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.CreateEmptyBlocksInterval, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "peer gossip sleep duration updated", "consensus.peer_gossip_sleep_duration", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.PeerGossipSleepDuration.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.PeerGossipSleepDuration, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "peer query majority sleep duration updated", "consensus.peer_query_maj23_sleep_duration", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.PeerQueryMaj23SleepDuration.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.PeerQueryMaj23SleepDuration, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, } @@ -303,18 +492,40 @@ func TestConfig_Get_Events(t *testing.T) { testTable := []testGetCase{ { - "event store type updated", + "event store type", + "tx_event_store.event_store_type", + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.TxEventStore.EventStoreType, + unmarshalJSONCommon[string](t, value), + ) + }, + false, + }, + { + "event store type, raw", "tx_event_store.event_store_type", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.TxEventStore.EventStoreType) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.TxEventStore.EventStoreType, + escapeNewline(value), + ) }, + true, }, { - "event store params updated", + "event store params", "tx_event_store.event_store_params", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.TxEventStore.Params)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.TxEventStore.Params, + unmarshalJSONCommon[types.EventStoreParams](t, value), + ) }, + false, }, } @@ -326,142 +537,196 @@ func TestConfig_Get_P2P(t *testing.T) { testTable := []testGetCase{ { - "root dir updated", + "root dir", "p2p.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.RootDir, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "listen address updated", + "root dir, raw", + "p2p.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.RootDir, escapeNewline(value)) + }, + true, + }, + { + "listen address", "p2p.laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.ListenAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ListenAddress, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "external address updated", + "listen address, raw", + "p2p.laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ListenAddress, escapeNewline(value)) + }, + true, + }, + { + "external address", + "p2p.external_address", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ExternalAddress, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "external address, raw", "p2p.external_address", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.ExternalAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ExternalAddress, escapeNewline(value)) }, + true, }, { - "seeds updated", + "seeds", "p2p.seeds", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.Seeds) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.Seeds, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "persistent peers updated", + "seeds, raw", + "p2p.seeds", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.Seeds, escapeNewline(value)) + }, + true, + }, + { + "persistent peers", + "p2p.persistent_peers", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PersistentPeers, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "persistent peers, raw", "p2p.persistent_peers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.PersistentPeers) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PersistentPeers, escapeNewline(value)) }, + true, }, { - "upnp toggle updated", + "upnp toggle", "p2p.upnp", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.UPNP) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.UPNP, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "max inbound peers updated", + "max inbound peers", "p2p.max_num_inbound_peers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.MaxNumInboundPeers)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.MaxNumInboundPeers, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "max outbound peers updated", + "max outbound peers", "p2p.max_num_outbound_peers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.MaxNumOutboundPeers)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.MaxNumOutboundPeers, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "flush throttle timeout updated", + "flush throttle timeout", "p2p.flush_throttle_timeout", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.FlushThrottleTimeout.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.FlushThrottleTimeout, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, { - "max package payload size updated", + "max package payload size", "p2p.max_packet_msg_payload_size", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.MaxPacketMsgPayloadSize)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.MaxPacketMsgPayloadSize, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "send rate updated", + "send rate", "p2p.send_rate", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.SendRate)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.SendRate, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "receive rate updated", + "receive rate", "p2p.recv_rate", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.RecvRate)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.RecvRate, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "pex reactor toggle updated", + "pex reactor toggle", "p2p.pex", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.PexReactor) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PexReactor, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "seed mode updated", + "seed mode", "p2p.seed_mode", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.SeedMode) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.SeedMode, unmarshalJSONCommon[bool](t, value)) + }, + false, + }, + { + "private peer IDs", + "p2p.private_peer_ids", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PrivatePeerIDs, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "private peer IDs updated", + "private peer IDs, raw", "p2p.private_peer_ids", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.PrivatePeerIDs) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PrivatePeerIDs, escapeNewline(value)) }, + true, }, { - "allow duplicate IP updated", + "allow duplicate IP", "p2p.allow_duplicate_ip", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.AllowDuplicateIP) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.AllowDuplicateIP, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "handshake timeout updated", + "handshake timeout", "p2p.handshake_timeout", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.HandshakeTimeout.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.HandshakeTimeout, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, { - "dial timeout updated", + "dial timeout", "p2p.dial_timeout", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.DialTimeout.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.DialTimeout, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, } @@ -473,105 +738,156 @@ func TestConfig_Get_RPC(t *testing.T) { testTable := []testGetCase{ { - "root dir updated", + "root dir", "rpc.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.RootDir, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "listen address updated", + "root dir, raw", + "rpc.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.RootDir, escapeNewline(value)) + }, + true, + }, + { + "listen address", "rpc.laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.ListenAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.ListenAddress, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "CORS Allowed Origins updated", + "listen address, raw", + "rpc.laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.ListenAddress, escapeNewline(value)) + }, + true, + }, + { + "CORS Allowed Origins", "rpc.cors_allowed_origins", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.RPC.CORSAllowedOrigins)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.CORSAllowedOrigins, unmarshalJSONCommon[[]string](t, value)) }, + false, }, { - "CORS Allowed Methods updated", + "CORS Allowed Methods", "rpc.cors_allowed_methods", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.RPC.CORSAllowedMethods)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.CORSAllowedMethods, unmarshalJSONCommon[[]string](t, value)) }, + false, }, { - "CORS Allowed Headers updated", + "CORS Allowed Headers", "rpc.cors_allowed_headers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.RPC.CORSAllowedHeaders)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.CORSAllowedHeaders, unmarshalJSONCommon[[]string](t, value)) }, + false, }, { - "GRPC listen address updated", + "GRPC listen address", "rpc.grpc_laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.GRPCListenAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.GRPCListenAddress, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "GRPC max open connections updated", + "GRPC listen address, raw", + "rpc.grpc_laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.GRPCListenAddress, escapeNewline(value)) + }, + true, + }, + { + "GRPC max open connections", "rpc.grpc_max_open_connections", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.GRPCMaxOpenConnections)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.GRPCMaxOpenConnections, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "unsafe value updated", + "unsafe value", "rpc.unsafe", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.RPC.Unsafe) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.Unsafe, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "rpc max open connections updated", + "rpc max open connections", "rpc.max_open_connections", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.MaxOpenConnections)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.MaxOpenConnections, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "tx commit broadcast timeout updated", + "tx commit broadcast timeout", "rpc.timeout_broadcast_tx_commit", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.TimeoutBroadcastTxCommit.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TimeoutBroadcastTxCommit, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, { - "max body bytes updated", + "max body bytes", "rpc.max_body_bytes", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.MaxBodyBytes)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.MaxBodyBytes, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "max header bytes updated", + "max header bytes", "rpc.max_header_bytes", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.MaxHeaderBytes)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.MaxHeaderBytes, unmarshalJSONCommon[int](t, value)) + }, + false, + }, + { + "TLS cert file", + "rpc.tls_cert_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSCertFile, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "TLS cert file updated", + "TLS cert file, raw", "rpc.tls_cert_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.TLSCertFile) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSCertFile, escapeNewline(value)) }, + true, }, { - "TLS key file updated", + "TLS key file", "rpc.tls_key_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.TLSKeyFile) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSKeyFile, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "TLS key file, raw", + "rpc.tls_key_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSKeyFile, escapeNewline(value)) + }, + true, }, } @@ -583,59 +899,76 @@ func TestConfig_Get_Mempool(t *testing.T) { testTable := []testGetCase{ { - "root dir updated", + "root dir", + "mempool.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.RootDir, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "root dir, raw", "mempool.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Mempool.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.RootDir, escapeNewline(value)) }, + true, }, { - "recheck flag updated", + "recheck flag", "mempool.recheck", - func(loadedCfg *config.Config, value string) { - boolVar, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVar, loadedCfg.Mempool.Recheck) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.Recheck, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "broadcast flag updated", + "broadcast flag", "mempool.broadcast", - func(loadedCfg *config.Config, value string) { - boolVar, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVar, loadedCfg.Mempool.Broadcast) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.Broadcast, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "WAL path updated", + "WAL path", + "mempool.wal_dir", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.WalPath, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "WAL path, raw", "mempool.wal_dir", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Mempool.WalPath) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.WalPath, escapeNewline(value)) }, + true, }, { - "size updated", + "size", "mempool.size", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.Mempool.Size)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.Size, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "max pending txs bytes updated", + "max pending txs bytes", "mempool.max_pending_txs_bytes", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.Mempool.MaxPendingTxsBytes)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.MaxPendingTxsBytes, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "cache size updated", + "cache size", "mempool.cache_size", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.Mempool.CacheSize)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.CacheSize, unmarshalJSONCommon[int](t, value)) }, + false, }, } diff --git a/gno.land/cmd/gnoland/config_init.go b/gno.land/cmd/gnoland/config_init.go index be7902b48cd..238db6bd348 100644 --- a/gno.land/cmd/gnoland/config_init.go +++ b/gno.land/cmd/gnoland/config_init.go @@ -3,17 +3,27 @@ package main import ( "context" "errors" + "flag" "fmt" + "os" + "path/filepath" "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/commands" + osm "github.com/gnolang/gno/tm2/pkg/os" ) var errInvalidConfigOutputPath = errors.New("invalid config output path provided") +type configInitCfg struct { + configCfg + + forceOverwrite bool +} + // newConfigInitCmd creates the config init command func newConfigInitCmd(io commands.IO) *commands.Command { - cfg := &configCfg{} + cfg := &configInitCfg{} cmd := commands.NewCommand( commands.Metadata{ @@ -32,15 +42,36 @@ func newConfigInitCmd(io commands.IO) *commands.Command { return cmd } -func execConfigInit(cfg *configCfg, io commands.IO) error { +func (c *configInitCfg) RegisterFlags(fs *flag.FlagSet) { + c.configCfg.RegisterFlags(fs) + + fs.BoolVar( + &c.forceOverwrite, + "force", + false, + "overwrite existing config.toml, if any", + ) +} + +func execConfigInit(cfg *configInitCfg, io commands.IO) error { // Check the config output path if cfg.configPath == "" { return errInvalidConfigOutputPath } + // Make sure overwriting the config is enabled + if osm.FileExists(cfg.configPath) && !cfg.forceOverwrite { + return errOverwriteNotEnabled + } + // Get the default config c := config.DefaultConfig() + // Make sure the path is created + if err := os.MkdirAll(filepath.Dir(cfg.configPath), 0o755); err != nil { + return fmt.Errorf("unable to create config dir, %w", err) + } + // Save the config to the path if err := config.WriteConfigFile(cfg.configPath, c); err != nil { return fmt.Errorf("unable to initialize config, %w", err) diff --git a/gno.land/cmd/gnoland/config_init_test.go b/gno.land/cmd/gnoland/config_init_test.go index c576aec1641..03a6d40a239 100644 --- a/gno.land/cmd/gnoland/config_init_test.go +++ b/gno.land/cmd/gnoland/config_init_test.go @@ -58,4 +58,73 @@ func TestConfig_Init(t *testing.T) { assert.NoError(t, cfg.ValidateBasic()) assert.Equal(t, cfg, config.DefaultConfig()) }) + + t.Run("unable to overwrite config", func(t *testing.T) { + t.Parallel() + + // Create a temporary directory + tempDir := t.TempDir() + path := filepath.Join(tempDir, "config.toml") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "config", + "init", + "--config-path", + path, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Verify the config is valid + cfg, err := config.LoadConfigFile(path) + require.NoError(t, err) + + assert.NoError(t, cfg.ValidateBasic()) + assert.Equal(t, cfg, config.DefaultConfig()) + + // Try to initialize again, expecting failure + cmd = newRootCmd(commands.NewTestIO()) + + cmdErr = cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errOverwriteNotEnabled) + }) + + t.Run("config overwritten", func(t *testing.T) { + t.Parallel() + + // Create a temporary directory + tempDir := t.TempDir() + path := filepath.Join(tempDir, "config.toml") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "config", + "init", + "--force", + "--config-path", + path, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Verify the config is valid + cfg, err := config.LoadConfigFile(path) + require.NoError(t, err) + + assert.NoError(t, cfg.ValidateBasic()) + assert.Equal(t, cfg, config.DefaultConfig()) + + // Try to initialize again, expecting success + cmd = newRootCmd(commands.NewTestIO()) + + cmdErr = cmd.ParseAndRun(context.Background(), args) + assert.NoError(t, cmdErr) + }) } diff --git a/gno.land/cmd/gnoland/config_set.go b/gno.land/cmd/gnoland/config_set.go index 47aa2e6559a..dd171970bf6 100644 --- a/gno.land/cmd/gnoland/config_set.go +++ b/gno.land/cmd/gnoland/config_set.go @@ -41,7 +41,7 @@ func execConfigEdit(cfg *configCfg, io commands.IO, args []string) error { // Load the config loadedCfg, err := config.LoadConfigFile(cfg.configPath) if err != nil { - return fmt.Errorf("unable to load config, %w", err) + return fmt.Errorf("%s, %w", tryConfigInit, err) } // Make sure the edit arguments are valid diff --git a/gno.land/cmd/gnoland/config_set_test.go b/gno.land/cmd/gnoland/config_set_test.go index b0898194d64..cb831f0e502 100644 --- a/gno.land/cmd/gnoland/config_set_test.go +++ b/gno.land/cmd/gnoland/config_set_test.go @@ -91,7 +91,7 @@ func TestConfig_Set_Invalid(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, "unable to load config") + assert.ErrorContains(t, cmdErr, tryConfigInit) }) t.Run("invalid config change", func(t *testing.T) { diff --git a/gno.land/cmd/gnoland/genesis_balances_add.go b/gno.land/cmd/gnoland/genesis_balances_add.go index c6d856f8363..4c8603c1273 100644 --- a/gno.land/cmd/gnoland/genesis_balances_add.go +++ b/gno.land/cmd/gnoland/genesis_balances_add.go @@ -43,7 +43,7 @@ func newBalancesAddCmd(rootCfg *balancesCfg, io commands.IO) *commands.Command { commands.Metadata{ Name: "add", ShortUsage: "balances add [flags]", - ShortHelp: "adds a new validator to the genesis.json", + ShortHelp: "adds balances to the genesis.json", }, cfg, func(ctx context.Context, _ []string) error { diff --git a/gno.land/cmd/gnoland/genesis_txs.go b/gno.land/cmd/gnoland/genesis_txs.go index 4d9ad8c11db..46b8d1bd29c 100644 --- a/gno.land/cmd/gnoland/genesis_txs.go +++ b/gno.land/cmd/gnoland/genesis_txs.go @@ -1,15 +1,21 @@ package main import ( + "errors" "flag" + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" ) type txsCfg struct { commonCfg } +var errInvalidGenesisStateType = errors.New("invalid genesis state type") + // newTxsCmd creates the genesis txs subcommand func newTxsCmd(io commands.IO) *commands.Command { cfg := &txsCfg{} @@ -29,6 +35,7 @@ func newTxsCmd(io commands.IO) *commands.Command { newTxsAddCmd(cfg, io), newTxsRemoveCmd(cfg, io), newTxsExportCmd(cfg, io), + newTxsListCmd(cfg, io), ) return cmd @@ -37,3 +44,33 @@ func newTxsCmd(io commands.IO) *commands.Command { func (c *txsCfg) RegisterFlags(fs *flag.FlagSet) { c.commonCfg.RegisterFlags(fs) } + +// appendGenesisTxs saves the given transactions to the genesis doc +func appendGenesisTxs(genesis *types.GenesisDoc, txs []std.Tx) error { + // Initialize the app state if it's not present + if genesis.AppState == nil { + genesis.AppState = gnoland.GnoGenesisState{} + } + + // Make sure the app state is the Gno genesis state + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + if !ok { + return errInvalidGenesisStateType + } + + // Left merge the transactions + fileTxStore := txStore(txs) + genesisTxStore := txStore(state.Txs) + + // The genesis transactions have preference with the order + // in the genesis.json + if err := genesisTxStore.leftMerge(fileTxStore); err != nil { + return err + } + + // Save the state + state.Txs = genesisTxStore + genesis.AppState = state + + return nil +} diff --git a/gno.land/cmd/gnoland/genesis_txs_add.go b/gno.land/cmd/gnoland/genesis_txs_add.go index 06019f7ef59..7e7fd25b21e 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add.go +++ b/gno.land/cmd/gnoland/genesis_txs_add.go @@ -1,141 +1,26 @@ package main import ( - "bufio" - "context" - "errors" - "fmt" - "io" - "os" - - "github.com/gnolang/gno/gno.land/pkg/gnoland" - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/std" -) - -var ( - errInvalidTxsFile = errors.New("unable to open transactions file") - errNoTxsFileSpecified = errors.New("no txs file specified") - errTxsParsingAborted = errors.New("transaction parsing aborted") ) // newTxsAddCmd creates the genesis txs add subcommand func newTxsAddCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { - return commands.NewCommand( + cmd := commands.NewCommand( commands.Metadata{ Name: "add", - ShortUsage: "txs add ", - ShortHelp: "imports transactions into the genesis.json", - LongHelp: "Imports the transactions from a tx-archive backup to the genesis.json", + ShortUsage: "txs add [flags] [...]", + ShortHelp: "adds transactions into the genesis.json", + LongHelp: "Adds initial transactions to the genesis.json", }, commands.NewEmptyConfig(), - func(ctx context.Context, args []string) error { - return execTxsAdd(ctx, txsCfg, io, args) - }, + commands.HelpExec, ) -} - -func execTxsAdd( - ctx context.Context, - cfg *txsCfg, - io commands.IO, - args []string, -) error { - // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) - if loadErr != nil { - return fmt.Errorf("unable to load genesis, %w", loadErr) - } - - // Open the transactions files - if len(args) == 0 { - return errNoTxsFileSpecified - } - - parsedTxs := make([]std.Tx, 0) - for _, file := range args { - file, loadErr := os.Open(file) - if loadErr != nil { - return fmt.Errorf("%w, %w", errInvalidTxsFile, loadErr) - } - - txs, err := getTransactionsFromFile(ctx, file) - if err != nil { - return fmt.Errorf("unable to read file, %w", err) - } - - parsedTxs = append(parsedTxs, txs...) - } - - // Initialize the app state if it's not present - if genesis.AppState == nil { - genesis.AppState = gnoland.GnoGenesisState{} - } - - state := genesis.AppState.(gnoland.GnoGenesisState) - // Left merge the transactions - fileTxStore := txStore(parsedTxs) - genesisTxStore := txStore(state.Txs) - - // The genesis transactions have preference with the order - // in the genesis.json - if err := genesisTxStore.leftMerge(fileTxStore); err != nil { - return err - } - - // Save the state - state.Txs = genesisTxStore - genesis.AppState = state - - // Save the updated genesis - if err := genesis.SaveAs(cfg.genesisPath); err != nil { - return fmt.Errorf("unable to save genesis.json, %w", err) - } - - io.Printfln( - "Saved %d transactions to genesis.json", - len(parsedTxs), + cmd.AddSubCommands( + newTxsAddSheetCmd(txsCfg, io), + newTxsAddPackagesCmd(txsCfg, io), ) - return nil -} - -// getTransactionsFromFile fetches the transactions from the -// specified reader -func getTransactionsFromFile(ctx context.Context, reader io.Reader) ([]std.Tx, error) { - txs := make([]std.Tx, 0) - - scanner := bufio.NewScanner(reader) - - for scanner.Scan() { - select { - case <-ctx.Done(): - return nil, errTxsParsingAborted - default: - // Parse the amino JSON - var tx std.Tx - - if err := amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { - return nil, fmt.Errorf( - "unable to unmarshal amino JSON, %w", - err, - ) - } - - txs = append(txs, tx) - } - } - - // Check for scanning errors - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf( - "error encountered while reading file, %w", - err, - ) - } - - return txs, nil + return cmd } diff --git a/gno.land/cmd/gnoland/genesis_txs_add_packages.go b/gno.land/cmd/gnoland/genesis_txs_add_packages.go new file mode 100644 index 00000000000..93246eadff5 --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_add_packages.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "errors" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var errInvalidPackageDir = errors.New("invalid package directory") + +var ( + genesisDeployAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 + genesisDeployFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) +) + +// newTxsAddPackagesCmd creates the genesis txs add packages subcommand +func newTxsAddPackagesCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "packages", + ShortUsage: "txs add packages ", + ShortHelp: "imports transactions from the given packages into the genesis.json", + LongHelp: "Imports the transactions from a given package directory recursively to the genesis.json", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execTxsAddPackages(txsCfg, io, args) + }, + ) +} + +func execTxsAddPackages( + cfg *txsCfg, + io commands.IO, + args []string, +) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Make sure the package dir is set + if len(args) == 0 { + return errInvalidPackageDir + } + + parsedTxs := make([]std.Tx, 0) + for _, path := range args { + // Generate transactions from the packages (recursively) + txs, err := gnoland.LoadPackagesFromDir(path, genesisDeployAddress, genesisDeployFee) + if err != nil { + return fmt.Errorf("unable to load txs from directory, %w", err) + } + + parsedTxs = append(parsedTxs, txs...) + } + + // Save the txs to the genesis.json + if err := appendGenesisTxs(genesis, parsedTxs); err != nil { + return fmt.Errorf("unable to append genesis transactions, %w", err) + } + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Saved %d transactions to genesis.json", + len(parsedTxs), + ) + + return nil +} diff --git a/gno.land/cmd/gnoland/genesis_txs_add_packages_test.go b/gno.land/cmd/gnoland/genesis_txs_add_packages_test.go new file mode 100644 index 00000000000..20c4f84c9ed --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_add_packages_test.go @@ -0,0 +1,133 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Txs_Add_Packages(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "add", + "packages", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid package dir", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errInvalidPackageDir.Error()) + }) + + t.Run("valid package", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Prepare the package + var ( + packagePath = "gno.land/p/demo/cuttlas" + dir = t.TempDir() + ) + + createFile := func(path, data string) { + file, err := os.Create(path) + require.NoError(t, err) + + _, err = file.WriteString(data) + require.NoError(t, err) + } + + // Create the gno.mod file + createFile( + filepath.Join(dir, "gno.mod"), + fmt.Sprintf("module %s\n", packagePath), + ) + + // Create a simple main.gno + createFile( + filepath.Join(dir, "main.gno"), + "package cuttlas\n\nfunc Example() string {\nreturn \"Manos arriba!\"\n}", + ) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + dir, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) + + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + require.Equal(t, 1, len(state.Txs)) + require.Equal(t, 1, len(state.Txs[0].Msgs)) + + msgAddPkg, ok := state.Txs[0].Msgs[0].(vmm.MsgAddPackage) + require.True(t, ok) + + assert.Equal(t, packagePath, msgAddPkg.Package.Path) + }) +} diff --git a/gno.land/cmd/gnoland/genesis_txs_add_sheet.go b/gno.land/cmd/gnoland/genesis_txs_add_sheet.go new file mode 100644 index 00000000000..261a050029c --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_add_sheet.go @@ -0,0 +1,87 @@ +package main + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ( + errInvalidTxsFile = errors.New("unable to open transactions file") + errNoTxsFileSpecified = errors.New("no txs file specified") +) + +// newTxsAddSheetCmd creates the genesis txs add sheet subcommand +func newTxsAddSheetCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "sheets", + ShortUsage: "txs add sheets ", + ShortHelp: "imports transactions from the given sheets into the genesis.json", + LongHelp: "Imports the transactions from a given transactions sheet to the genesis.json", + }, + commands.NewEmptyConfig(), + func(ctx context.Context, args []string) error { + return execTxsAddSheet(ctx, txsCfg, io, args) + }, + ) +} + +func execTxsAddSheet( + ctx context.Context, + cfg *txsCfg, + io commands.IO, + args []string, +) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Open the transactions files + if len(args) == 0 { + return errNoTxsFileSpecified + } + + parsedTxs := make([]std.Tx, 0) + for _, file := range args { + file, loadErr := os.Open(file) + if loadErr != nil { + return fmt.Errorf("%w, %w", errInvalidTxsFile, loadErr) + } + + txs, err := std.ParseTxs(ctx, file) + if err != nil { + return fmt.Errorf("unable to parse file, %w", err) + } + + if err = file.Close(); err != nil { + return fmt.Errorf("unable to gracefully close file, %w", err) + } + + parsedTxs = append(parsedTxs, txs...) + } + + // Save the txs to the genesis.json + if err := appendGenesisTxs(genesis, parsedTxs); err != nil { + return fmt.Errorf("unable to append genesis transactions, %w", err) + } + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Saved %d transactions to genesis.json", + len(parsedTxs), + ) + + return nil +} diff --git a/gno.land/cmd/gnoland/genesis_txs_add_test.go b/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go similarity index 97% rename from gno.land/cmd/gnoland/genesis_txs_add_test.go rename to gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go index 048aaf889be..1d49422afd1 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_test.go +++ b/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go @@ -62,7 +62,7 @@ func encodeDummyTxs(t *testing.T, txs []std.Tx) []string { return encodedTxs } -func TestGenesis_Txs_Add(t *testing.T) { +func TestGenesis_Txs_Add_Sheets(t *testing.T) { t.Parallel() t.Run("invalid genesis file", func(t *testing.T) { @@ -74,6 +74,7 @@ func TestGenesis_Txs_Add(t *testing.T) { "genesis", "txs", "add", + "sheets", "--genesis-path", "dummy-path", } @@ -98,6 +99,7 @@ func TestGenesis_Txs_Add(t *testing.T) { "genesis", "txs", "add", + "sheets", "--genesis-path", tempGenesis.Name(), "dummy-tx-file", @@ -123,6 +125,7 @@ func TestGenesis_Txs_Add(t *testing.T) { "genesis", "txs", "add", + "sheets", "--genesis-path", tempGenesis.Name(), } @@ -147,6 +150,7 @@ func TestGenesis_Txs_Add(t *testing.T) { "genesis", "txs", "add", + "sheets", "--genesis-path", tempGenesis.Name(), tempGenesis.Name(), // invalid txs file @@ -154,7 +158,7 @@ func TestGenesis_Txs_Add(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, "unable to read file") + assert.ErrorContains(t, cmdErr, "unable to parse file") }) t.Run("valid txs file", func(t *testing.T) { @@ -187,6 +191,7 @@ func TestGenesis_Txs_Add(t *testing.T) { "genesis", "txs", "add", + "sheets", "--genesis-path", tempGenesis.Name(), txsFile.Name(), @@ -246,6 +251,7 @@ func TestGenesis_Txs_Add(t *testing.T) { "genesis", "txs", "add", + "sheets", "--genesis-path", tempGenesis.Name(), txsFile.Name(), diff --git a/gno.land/cmd/gnoland/genesis_txs_list.go b/gno.land/cmd/gnoland/genesis_txs_list.go new file mode 100644 index 00000000000..c68fbc30803 --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_list.go @@ -0,0 +1,55 @@ +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +var ErrWrongGenesisType = errors.New("genesis state is not using the correct Gno Genesis type") + +// newTxsListCmd list all transactions on the specified genesis file +func newTxsListCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + Name: "list", + ShortUsage: "txs list [flags] [...]", + ShortHelp: "lists transactions existing on genesis.json", + LongHelp: "Lists transactions existing on genesis.json", + }, + commands.NewEmptyConfig(), + func(ctx context.Context, args []string) error { + return execTxsListCmd(io, txsCfg) + }, + ) + + return cmd +} + +func execTxsListCmd(io commands.IO, cfg *txsCfg) error { + genesis, err := types.GenesisDocFromFile(cfg.genesisPath) + if err != nil { + return fmt.Errorf("%w, %w", errUnableToLoadGenesis, err) + } + + gs, ok := genesis.AppState.(gnoland.GnoGenesisState) + if !ok { + return ErrWrongGenesisType + } + + b, err := amino.MarshalJSONIndent(gs.Txs, "", " ") + if err != nil { + return errors.New("error marshalling data to amino JSON") + } + + buf := bytes.NewBuffer(b) + _, err = buf.WriteTo(io.Out()) + + return err +} diff --git a/gno.land/cmd/gnoland/genesis_txs_list_test.go b/gno.land/cmd/gnoland/genesis_txs_list_test.go new file mode 100644 index 00000000000..d18c2f4d641 --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_list_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "bytes" + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/testutils" +) + +func TestGenesis_List_All(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis path", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "list", + "--genesis-path", + "", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errUnableToLoadGenesis) + }) + + t.Run("list all txs", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Generate dummy txs + txs := generateDummyTxs(t, 10) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Txs: txs, + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + cio := commands.NewTestIO() + buf := bytes.NewBuffer(nil) + cio.SetOut(commands.WriteNopCloser(buf)) + + cmd := newRootCmd(cio) + args := []string{ + "genesis", + "txs", + "list", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + require.Len(t, buf.String(), 4442) + }) +} diff --git a/gno.land/cmd/gnoland/genesis_validator.go b/gno.land/cmd/gnoland/genesis_validator.go index cf619ac40dc..91d3e4af7dd 100644 --- a/gno.land/cmd/gnoland/genesis_validator.go +++ b/gno.land/cmd/gnoland/genesis_validator.go @@ -44,6 +44,6 @@ func (c *validatorCfg) RegisterFlags(fs *flag.FlagSet) { &c.address, "address", "", - "the output path for the genesis.json", + "the gno bech32 address of the validator", ) } diff --git a/gno.land/cmd/gnoland/root.go b/gno.land/cmd/gnoland/root.go index d670151b358..8df716b1fed 100644 --- a/gno.land/cmd/gnoland/root.go +++ b/gno.land/cmd/gnoland/root.go @@ -33,9 +33,9 @@ func newRootCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newStartCmd(io), + newGenesisCmd(io), newSecretsCmd(io), newConfigCmd(io), - newGenesisCmd(io), ) return cmd diff --git a/gno.land/cmd/gnoland/secrets.go b/gno.land/cmd/gnoland/secrets.go index 36113a3e207..6fbce52c638 100644 --- a/gno.land/cmd/gnoland/secrets.go +++ b/gno.land/cmd/gnoland/secrets.go @@ -3,7 +3,9 @@ package main import ( "errors" "flag" + "path/filepath" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -13,16 +15,15 @@ var ( ) const ( - defaultSecretsDir = "./secrets" defaultValidatorKeyName = "priv_validator_key.json" defaultNodeKeyName = "node_key.json" defaultValidatorStateName = "priv_validator_state.json" ) const ( - nodeKeyKey = "NodeKey" - validatorPrivateKeyKey = "ValidatorPrivateKey" - validatorStateKey = "ValidatorState" + nodeIDKey = "node_id" + validatorPrivateKeyKey = "validator_key" + validatorStateKey = "validator_state" ) // newSecretsCmd creates the secrets root command @@ -58,7 +59,46 @@ func (c *commonAllCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.dataDir, "data-dir", - defaultSecretsDir, + constructSecretsPath(defaultNodeDir), "the secrets output directory", ) } + +// constructSecretsPath constructs the default secrets path, using +// the given node directory +func constructSecretsPath(nodeDir string) string { + return filepath.Join( + nodeDir, + config.DefaultSecretsDir, + ) +} + +type ( + secrets struct { + ValidatorKeyInfo *validatorKeyInfo `json:"validator_key,omitempty" toml:"validator_key" comment:"the validator private key info"` + ValidatorStateInfo *validatorStateInfo `json:"validator_state,omitempty" toml:"validator_state" comment:"the last signed validator state info"` + NodeIDInfo *nodeIDInfo `json:"node_id,omitempty" toml:"node_id" comment:"the derived node ID info"` + } + + // NOTE: keep in sync with tm2/pkg/bft/privval/file.go + validatorKeyInfo struct { + Address string `json:"address" toml:"address" comment:"the validator address"` + PubKey string `json:"pub_key" toml:"pub_key" comment:"the validator public key"` + } + + // NOTE: keep in sync with tm2/pkg/bft/privval/file.go + validatorStateInfo struct { + Height int64 `json:"height" toml:"height" comment:"the height of the last sign"` + Round int `json:"round" toml:"round" comment:"the round of the last sign"` + Step int8 `json:"step" toml:"step" comment:"the step of the last sign"` + + Signature []byte `json:"signature,omitempty" toml:"signature,omitempty" comment:"the signature of the last sign"` + SignBytes []byte `json:"sign_bytes,omitempty" toml:"sign_bytes,omitempty" comment:"the raw signature bytes of the last sign"` + } + + // NOTE: keep in sync with tm2/pkg/p2p/key.go + nodeIDInfo struct { + ID string `json:"id" toml:"id" comment:"the node ID derived from the private key"` + P2PAddress string `json:"p2p_address" toml:"p2p_address" comment:"the node's constructed P2P address'"` + } +) diff --git a/gno.land/cmd/gnoland/secrets_common.go b/gno.land/cmd/gnoland/secrets_common.go index 588307b9b8e..d40e90f6b48 100644 --- a/gno.land/cmd/gnoland/secrets_common.go +++ b/gno.land/cmd/gnoland/secrets_common.go @@ -168,14 +168,14 @@ func verifySecretsKey(args []string) error { // Verify the set key key := args[0] - if key != nodeKeyKey && + if key != nodeIDKey && key != validatorPrivateKeyKey && key != validatorStateKey { return fmt.Errorf( "invalid secrets key value [%s, %s, %s]", validatorPrivateKeyKey, validatorStateKey, - nodeKeyKey, + nodeIDKey, ) } @@ -187,7 +187,7 @@ func getAvailableSecretsKeys() string { return fmt.Sprintf( "[%s, %s, %s]", validatorPrivateKeyKey, - nodeKeyKey, + nodeIDKey, validatorStateKey, ) } diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 699450702b4..47de7a46283 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -2,18 +2,25 @@ package main import ( "context" + "errors" "flag" "fmt" "path/filepath" - "text/tabwriter" + "strings" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" + osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/p2p" ) +var errInvalidSecretsGetArgs = errors.New("invalid number of secrets get arguments provided") + type secretsGetCfg struct { commonAllCfg + + raw bool } // newSecretsGetCmd creates the secrets get command @@ -24,12 +31,9 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "get", ShortUsage: "secrets get [flags] []", - ShortHelp: "shows all Gno secrets present in a common directory", - LongHelp: fmt.Sprintf( - "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: %s", - getAvailableSecretsKeys(), - ), + ShortHelp: "shows the Gno secrets present in a common directory", + LongHelp: "shows the validator private key, the node p2p key and the validator's last sign state at the given path " + + "by fetching the option specified at ", }, cfg, func(_ context.Context, args []string) error { @@ -42,6 +46,13 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { func (c *secretsGetCfg) RegisterFlags(fs *flag.FlagSet) { c.commonAllCfg.RegisterFlags(fs) + + fs.BoolVar( + &c.raw, + "raw", + false, + "output raw string values, rather than as JSON strings", + ) } func execSecretsGet(cfg *secretsGetCfg, args []string, io commands.IO) error { @@ -50,154 +61,144 @@ func execSecretsGet(cfg *secretsGetCfg, args []string, io commands.IO) error { return errInvalidDataDir } - // Verify the secrets key - if err := verifySecretsKey(args); err != nil { - return err + // Make sure the get arguments are valid + if len(args) > 1 { + return errInvalidSecretsGetArgs } - var key string + // Load the secrets from the dir + loadedSecrets, err := loadSecrets(cfg.dataDir) + if err != nil { + return err + } - if len(args) > 0 { - key = args[0] + // Find and print the secrets value, if any + if err := printKeyValue(loadedSecrets, cfg.raw, io, args...); err != nil { + return fmt.Errorf("unable to get secrets value, %w", err) } - // Construct the paths + return nil +} + +// loadSecrets loads the secrets from the specified data directory +func loadSecrets(dirPath string) (*secrets, error) { + // Construct the file paths var ( - validatorKeyPath = filepath.Join(cfg.dataDir, defaultValidatorKeyName) - validatorStatePath = filepath.Join(cfg.dataDir, defaultValidatorStateName) - nodeKeyPath = filepath.Join(cfg.dataDir, defaultNodeKeyName) + validatorKeyPath = filepath.Join(dirPath, defaultValidatorKeyName) + validatorStatePath = filepath.Join(dirPath, defaultValidatorStateName) + nodeKeyPath = filepath.Join(dirPath, defaultNodeKeyName) ) - switch key { - case validatorPrivateKeyKey: - // Show the validator's key info - return readAndShowValidatorKey(validatorKeyPath, io) - case validatorStateKey: - // Show the validator's last sign state - return readAndShowValidatorState(validatorStatePath, io) - case nodeKeyKey: - // Show the node's p2p info - return readAndShowNodeKey(nodeKeyPath, io) - default: - // Show the node's p2p info - if err := readAndShowNodeKey(nodeKeyPath, io); err != nil { - return err - } + var ( + vkInfo *validatorKeyInfo + vsInfo *validatorStateInfo + niInfo *nodeIDInfo - // Show the validator's key info - if err := readAndShowValidatorKey(validatorKeyPath, io); err != nil { - return err - } + err error + ) - // Show the validator's last sign state - return readAndShowValidatorState(validatorStatePath, io) + // Load the secrets + if osm.FileExists(validatorKeyPath) { + vkInfo, err = readValidatorKey(validatorKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to load secrets, %w", err) + } } -} -// readAndShowValidatorKey reads and shows the validator key from the given path -func readAndShowValidatorKey(path string, io commands.IO) error { - validatorKey, err := readSecretData[privval.FilePVKey](path) - if err != nil { - return fmt.Errorf("unable to read validator key, %w", err) + if osm.FileExists(validatorStatePath) { + vsInfo, err = readValidatorState(validatorStatePath) + if err != nil { + return nil, fmt.Errorf("unable to load secrets, %w", err) + } } - w := tabwriter.NewWriter(io.Out(), 0, 0, 2, ' ', 0) - - if _, err := fmt.Fprintf(w, "[Validator Key Info]\n\n"); err != nil { - return err + if osm.FileExists(nodeKeyPath) { + niInfo, err = readNodeID(nodeKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to load secrets, %w", err) + } } - if _, err := fmt.Fprintf(w, "Address:\t%s\n", validatorKey.Address.String()); err != nil { - return err - } + return &secrets{ + ValidatorKeyInfo: vkInfo, + ValidatorStateInfo: vsInfo, + NodeIDInfo: niInfo, + }, nil +} - if _, err := fmt.Fprintf(w, "Public Key:\t%s\n", validatorKey.PubKey.String()); err != nil { - return err +// readValidatorKey reads the validator key from the given path +func readValidatorKey(path string) (*validatorKeyInfo, error) { + validatorKey, err := readSecretData[privval.FilePVKey](path) + if err != nil { + return nil, fmt.Errorf("unable to read validator key, %w", err) } - return w.Flush() + return &validatorKeyInfo{ + Address: validatorKey.Address.String(), + PubKey: validatorKey.PubKey.String(), + }, nil } -// readAndShowValidatorState reads and shows the validator state from the given path -func readAndShowValidatorState(path string, io commands.IO) error { +// readValidatorState reads the validator state from the given path +func readValidatorState(path string) (*validatorStateInfo, error) { validatorState, err := readSecretData[privval.FilePVLastSignState](path) if err != nil { - return fmt.Errorf("unable to read validator state, %w", err) + return nil, fmt.Errorf("unable to read validator state, %w", err) } - w := tabwriter.NewWriter(io.Out(), 0, 0, 2, ' ', 0) - - if _, err := fmt.Fprintf(w, "[Last Validator Sign State Info]\n\n"); err != nil { - return err - } - - if _, err := fmt.Fprintf( - w, - "Height:\t%d\n", - validatorState.Height, - ); err != nil { - return err - } + return &validatorStateInfo{ + Height: validatorState.Height, + Round: validatorState.Round, + Step: validatorState.Step, + Signature: validatorState.Signature, + SignBytes: validatorState.SignBytes, + }, nil +} - if _, err := fmt.Fprintf( - w, - "Round:\t%d\n", - validatorState.Round, - ); err != nil { - return err +// readNodeID reads the node p2p info from the given path +func readNodeID(path string) (*nodeIDInfo, error) { + nodeKey, err := readSecretData[p2p.NodeKey](path) + if err != nil { + return nil, fmt.Errorf("unable to read node key, %w", err) } - if _, err := fmt.Fprintf( - w, - "Step:\t%d\n", - validatorState.Step, - ); err != nil { - return err - } + // Construct the config path + var ( + nodeDir = filepath.Join(filepath.Dir(path), "..") + configPath = constructConfigPath(nodeDir) - if validatorState.Signature != nil { - if _, err := fmt.Fprintf( - w, - "Signature:\t%X\n", - validatorState.Signature, - ); err != nil { - return err - } - } + cfg = config.DefaultConfig() + ) - if validatorState.SignBytes != nil { - if _, err := fmt.Fprintf( - w, - "Sign Bytes:\t%X\n", - validatorState.SignBytes, - ); err != nil { - return err + // Check if there is an existing config file + if osm.FileExists(configPath) { + // Attempt to grab the config from disk + cfg, err = config.LoadConfig(nodeDir) + if err != nil { + return nil, fmt.Errorf("unable to load config file, %w", err) } } - return w.Flush() + return &nodeIDInfo{ + ID: nodeKey.ID().String(), + P2PAddress: constructP2PAddress(nodeKey.ID(), cfg.P2P.ListenAddress), + }, nil } -// readAndShowNodeKey reads and shows the node p2p key from the given path -func readAndShowNodeKey(path string, io commands.IO) error { - nodeKey, err := readSecretData[p2p.NodeKey](path) - if err != nil { - return fmt.Errorf("unable to read node key, %w", err) - } - - w := tabwriter.NewWriter(io.Out(), 0, 0, 2, ' ', 0) - - if _, err := fmt.Fprintf(w, "[Node P2P Info]\n\n"); err != nil { - return err - } +// constructP2PAddress constructs the P2P address other nodes can use +// to connect directly +func constructP2PAddress(nodeID p2p.ID, listenAddress string) string { + var ( + address string + parts = strings.SplitN(listenAddress, "://", 2) + ) - if _, err := fmt.Fprintf( - w, - "Node ID:\t%s\n", - nodeKey.ID(), - ); err != nil { - return err + switch len(parts) { + case 2: + address = parts[1] + default: + address = listenAddress } - return w.Flush() + return fmt.Sprintf("%s@%s", nodeID, address) } diff --git a/gno.land/cmd/gnoland/secrets_get_test.go b/gno.land/cmd/gnoland/secrets_get_test.go index 20f1eb2ef35..66e6e3509fc 100644 --- a/gno.land/cmd/gnoland/secrets_get_test.go +++ b/gno.land/cmd/gnoland/secrets_get_test.go @@ -3,11 +3,14 @@ package main import ( "bytes" "context" + "encoding/json" "fmt" + "os" "path/filepath" "strconv" "testing" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/p2p" @@ -95,6 +98,13 @@ func TestSecrets_Get_All(t *testing.T) { nodeKey.ID().String(), ) + // Make sure the node p2p address is displayed + assert.Contains( + t, + output, + constructP2PAddress(nodeKey.ID(), config.DefaultConfig().P2P.ListenAddress), + ) + // Make sure the private key info is displayed assert.Contains( t, @@ -142,10 +152,10 @@ func TestSecrets_Get_All(t *testing.T) { }) } -func TestSecrets_Get_Single(t *testing.T) { +func TestSecrets_Get_ValidatorKeyInfo(t *testing.T) { t.Parallel() - t.Run("validator key shown", func(t *testing.T) { + t.Run("validator key info", func(t *testing.T) { t.Parallel() dirPath := t.TempDir() @@ -172,23 +182,175 @@ func TestSecrets_Get_Single(t *testing.T) { // Run the command require.NoError(t, cmd.ParseAndRun(context.Background(), args)) - output := mockOutput.String() + var vk validatorKeyInfo + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &vk)) // Make sure the private key info is displayed - assert.Contains( + assert.Equal( t, - output, validKey.Address.String(), + vk.Address, ) - assert.Contains( + assert.Equal( t, - output, validKey.PubKey.String(), + vk.PubKey, ) }) - t.Run("validator state shown", func(t *testing.T) { + t.Run("validator key address", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "address"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var address string + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &address)) + + assert.Equal( + t, + validKey.Address.String(), + address, + ) + }) + + t.Run("validator key address, raw", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "address"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + validKey.Address.String(), + escapeNewline(mockOutput.Bytes()), + ) + }) + + t.Run("validator key pubkey", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "pub_key"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var address string + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &address)) + + assert.Equal( + t, + validKey.PubKey.String(), + address, + ) + }) + + t.Run("validator key pubkey, raw", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "pub_key"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + validKey.PubKey.String(), + escapeNewline(mockOutput.Bytes()), + ) + }) +} + +func TestSecrets_Get_ValidatorStateInfo(t *testing.T) { + t.Parallel() + + t.Run("validator state info", func(t *testing.T) { t.Parallel() dirPath := t.TempDir() @@ -215,31 +377,141 @@ func TestSecrets_Get_Single(t *testing.T) { // Run the command require.NoError(t, cmd.ParseAndRun(context.Background(), args)) - output := mockOutput.String() + var vs validatorStateInfo + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &vs)) // Make sure the state info is displayed - assert.Contains( + assert.Equal( t, - output, - fmt.Sprintf("%d", validState.Step), + validState.Step, + vs.Step, ) - assert.Contains( + assert.Equal( t, - output, - fmt.Sprintf("%d", validState.Height), + validState.Height, + vs.Height, ) - assert.Contains( + assert.Equal( t, - output, - strconv.Itoa(validState.Round), + validState.Round, + vs.Round, + ) + }) + + t.Run("validator state info height", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + statePath := filepath.Join(dirPath, defaultValidatorStateName) + + validState := generateLastSignValidatorState() + + require.NoError(t, saveSecretData(validState, statePath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorStateKey, "height"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + fmt.Sprintf("%d\n", validState.Height), + mockOutput.String(), + ) + }) + + t.Run("validator state info round", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + statePath := filepath.Join(dirPath, defaultValidatorStateName) + + validState := generateLastSignValidatorState() + + require.NoError(t, saveSecretData(validState, statePath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorStateKey, "round"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + fmt.Sprintf("%d\n", validState.Round), + mockOutput.String(), ) }) - t.Run("node key shown", func(t *testing.T) { + t.Run("validator state info step", func(t *testing.T) { t.Parallel() + dirPath := t.TempDir() + statePath := filepath.Join(dirPath, defaultValidatorStateName) + + validState := generateLastSignValidatorState() + + require.NoError(t, saveSecretData(validState, statePath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorStateKey, "step"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + fmt.Sprintf("%d\n", validState.Step), + mockOutput.String(), + ) + }) +} + +func TestSecrets_Get_NodeIDInfo(t *testing.T) { + t.Parallel() + + t.Run("node ID info, default config", func(t *testing.T) { + t.Parallel() + + cfg := config.DefaultConfig() + dirPath := t.TempDir() nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) @@ -258,19 +530,237 @@ func TestSecrets_Get_Single(t *testing.T) { "get", "--data-dir", dirPath, - nodeKeyKey, + nodeIDKey, } // Run the command require.NoError(t, cmd.ParseAndRun(context.Background(), args)) - output := mockOutput.String() + var ni nodeIDInfo + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &ni)) // Make sure the node p2p key is displayed - assert.Contains( + assert.Equal( t, + validNodeKey.ID().String(), + ni.ID, + ) + + // Make sure the default node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + ni.P2PAddress, + ) + }) + + t.Run("node ID info, existing config", func(t *testing.T) { + t.Parallel() + + var ( + dirPath = t.TempDir() + configPath = constructConfigPath(dirPath) + secretsPath = constructSecretsPath(dirPath) + nodeKeyPath = filepath.Join(secretsPath, defaultNodeKeyName) + ) + + // Ensure the sub-dirs exist + require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755)) + require.NoError(t, os.MkdirAll(secretsPath, 0o755)) + + // Set up the config + cfg := config.DefaultConfig() + cfg.P2P.ListenAddress = "tcp://127.0.0.1:2525" + + require.NoError(t, config.WriteConfigFile(configPath, cfg)) + + validNodeKey := generateNodeKey() + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + secretsPath, + nodeIDKey, + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var ni nodeIDInfo + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &ni)) + + // Make sure the node p2p key is displayed + assert.Equal( + t, + validNodeKey.ID().String(), + ni.ID, + ) + + // Make sure the custom node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + ni.P2PAddress, + ) + }) + + t.Run("ID", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "id"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var output string + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &output)) + + // Make sure the node p2p key is displayed + assert.Equal( + t, + validNodeKey.ID().String(), output, + ) + }) + + t.Run("ID, raw", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "id"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + // Make sure the node p2p key is displayed + assert.Equal( + t, validNodeKey.ID().String(), + escapeNewline(mockOutput.Bytes()), + ) + }) + + t.Run("P2P Address", func(t *testing.T) { + t.Parallel() + + cfg := config.DefaultConfig() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "p2p_address"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var output string + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &output)) + + // Make sure the custom node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + output, + ) + }) + + t.Run("P2P Address, raw", func(t *testing.T) { + t.Parallel() + + cfg := config.DefaultConfig() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "p2p_address"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + // Make sure the custom node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + escapeNewline(mockOutput.Bytes()), ) }) } diff --git a/gno.land/cmd/gnoland/secrets_init.go b/gno.land/cmd/gnoland/secrets_init.go index 55be73c22fc..58dd0783f66 100644 --- a/gno.land/cmd/gnoland/secrets_init.go +++ b/gno.land/cmd/gnoland/secrets_init.go @@ -88,55 +88,54 @@ func execSecretsInit(cfg *secretsInitCfg, args []string, io commands.IO) error { switch key { case validatorPrivateKeyKey: if osm.FileExists(validatorKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled + return fmt.Errorf("unable to overwrite validator key, %w", errOverwriteNotEnabled) } // Initialize and save the validator's private key return initAndSaveValidatorKey(validatorKeyPath, io) - case nodeKeyKey: + case nodeIDKey: if osm.FileExists(nodeKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled + return fmt.Errorf("unable to overwrite the node' p2p key, %w", errOverwriteNotEnabled) } // Initialize and save the node's p2p key return initAndSaveNodeKey(nodeKeyPath, io) case validatorStateKey: if osm.FileExists(validatorStatePath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled + return fmt.Errorf("unable to overwrite validator last sign state, %w", errOverwriteNotEnabled) } // Initialize and save the validator's last sign state return initAndSaveValidatorState(validatorStatePath, io) default: - // Check if the validator key should be overwritten - if osm.FileExists(validatorKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled - } - - // Check if the validator state should be overwritten - if osm.FileExists(validatorStatePath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled - } - - // Check if the node key should be overwritten - if osm.FileExists(nodeKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled - } - // No key provided, initialize everything - // Initialize and save the validator's private key - if err := initAndSaveValidatorKey(validatorKeyPath, io); err != nil { - return err - } - - // Initialize and save the validator's last sign state - if err := initAndSaveValidatorState(validatorStatePath, io); err != nil { - return err - } + return errors.Join( + overwriteGuard(validatorKeyPath, initAndSaveValidatorKey, cfg.forceOverwrite, io), + overwriteGuard(validatorStatePath, initAndSaveValidatorState, cfg.forceOverwrite, io), + overwriteGuard(nodeKeyPath, initAndSaveNodeKey, cfg.forceOverwrite, io), + ) + } +} - // Initialize and save the node's p2p key - return initAndSaveNodeKey(nodeKeyPath, io) +// overwriteGuard guards against unwanted secret overwrites, +// and executes the secret initialization if the secret is not present +func overwriteGuard( + path string, + initFn func(string, commands.IO) error, + overwriteEnabled bool, + io commands.IO, +) error { + // Check if the secret already exists + if osm.FileExists(path) && !overwriteEnabled { + return fmt.Errorf( + "unable to overwrite secret at %q, %w", + path, + errOverwriteNotEnabled, + ) } + + // Secret doesn't exist, initialize it + return initFn(path, io) } // initAndSaveValidatorKey generates a validator private key and saves it to the given path diff --git a/gno.land/cmd/gnoland/secrets_init_test.go b/gno.land/cmd/gnoland/secrets_init_test.go index 4a707778cc6..20e061447f5 100644 --- a/gno.land/cmd/gnoland/secrets_init_test.go +++ b/gno.land/cmd/gnoland/secrets_init_test.go @@ -149,7 +149,7 @@ func TestSecrets_Init_Single(t *testing.T) { }, { "node p2p key initialized", - nodeKeyKey, + nodeIDKey, defaultNodeKeyName, verifyNodeKey, }, @@ -207,7 +207,7 @@ func TestSecrets_Init_Single_Overwrite(t *testing.T) { }, { "node p2p key not overwritten", - nodeKeyKey, + nodeIDKey, defaultNodeKeyName, }, } diff --git a/gno.land/cmd/gnoland/secrets_verify.go b/gno.land/cmd/gnoland/secrets_verify.go index 7e6c154d1ac..32e563c1c6f 100644 --- a/gno.land/cmd/gnoland/secrets_verify.go +++ b/gno.land/cmd/gnoland/secrets_verify.go @@ -87,7 +87,7 @@ func execSecretsVerify(cfg *secretsVerifyCfg, args []string, io commands.IO) err } return nil - case nodeKeyKey: + case nodeIDKey: return readAndVerifyNodeKey(nodeKeyPath, io) default: // Validate the validator's private key diff --git a/gno.land/cmd/gnoland/secrets_verify_test.go b/gno.land/cmd/gnoland/secrets_verify_test.go index 77ab8523d40..513d7c8b503 100644 --- a/gno.land/cmd/gnoland/secrets_verify_test.go +++ b/gno.land/cmd/gnoland/secrets_verify_test.go @@ -360,7 +360,7 @@ func TestSecrets_Verify_Single(t *testing.T) { "verify", "--data-dir", dirPath, - nodeKeyKey, + nodeIDKey, } // Run the command diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 11006d69246..21f0cb4b1a6 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -5,8 +5,12 @@ import ( "errors" "flag" "fmt" + "io" + "os" + "os/signal" "path/filepath" "strings" + "syscall" "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" @@ -16,18 +20,20 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/privval" - "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" - "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" - eventstorecfg "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/telemetry" + "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +const defaultNodeDir = "gnoland-data" + +var errMissingGenesis = errors.New("missing genesis.json") + var startGraphic = strings.ReplaceAll(` __ __ ___ ____ ___ / /__ ____ ___/ / @@ -37,21 +43,17 @@ var startGraphic = strings.ReplaceAll(` `, "'", "`") type startCfg struct { - gnoRootDir string - skipFailingGenesisTxs bool - skipStart bool - genesisBalancesFile string - genesisTxsFile string + gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisBalancesFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisTxsFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisRemote string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 genesisFile string chainID string - genesisRemote string dataDir string genesisMaxVMCycles int64 config string - - txEventStoreType string - txEventStorePath string - nodeConfigPath string + lazyInit bool logLevel string logFormat string @@ -64,11 +66,12 @@ func newStartCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "start", ShortUsage: "start [flags]", - ShortHelp: "run the full node", + ShortHelp: "starts the Gnoland blockchain node", + LongHelp: "Starts the Gnoland blockchain node, with accompanying setup", }, cfg, - func(_ context.Context, _ []string) error { - return execStart(cfg, io) + func(ctx context.Context, _ []string) error { + return execStart(ctx, cfg, io) }, ) } @@ -85,13 +88,6 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "don't panic when replaying invalid genesis txs", ) - fs.BoolVar( - &c.skipStart, - "skip-start", - false, - "quit after initialization, don't start the node", - ) - fs.StringVar( &c.genesisBalancesFile, "genesis-balances-file", @@ -130,7 +126,7 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.dataDir, "data-dir", - "gnoland-data", + defaultNodeDir, "the path to the node's data directory", ) @@ -155,36 +151,6 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "the flag config file (optional)", ) - fs.StringVar( - &c.nodeConfigPath, - "config-path", - "", - "the node TOML config file path (optional)", - ) - - fs.StringVar( - &c.txEventStoreType, - "tx-event-store-type", - null.EventStoreType, - fmt.Sprintf( - "type of transaction event store [%s]", - strings.Join( - []string{ - null.EventStoreType, - file.EventStoreType, - }, - ", ", - ), - ), - ) - - fs.StringVar( - &c.txEventStorePath, - "tx-event-store-path", - "", - fmt.Sprintf("path for the file tx event store (required if event store is '%s')", file.EventStoreType), - ) - fs.StringVar( &c.logLevel, "log-level", @@ -198,9 +164,16 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { log.ConsoleFormat.String(), "log format for the gnoland node", ) + + fs.BoolVar( + &c.lazyInit, + "lazy", + false, + "flag indicating if lazy init is enabled. Generates the node secrets, configuration, and genesis.json", + ) } -func execStart(c *startCfg, io commands.IO) error { +func execStart(ctx context.Context, c *startCfg, io commands.IO) error { // Get the absolute path to the node's data directory nodeDir, err := filepath.Abs(c.dataDir) if err != nil { @@ -213,103 +186,191 @@ func execStart(c *startCfg, io commands.IO) error { return fmt.Errorf("unable to get absolute path for the genesis.json, %w", err) } - var ( - cfg *config.Config - loadCfgErr error - ) - - // Set the node configuration - if c.nodeConfigPath != "" { - // Load the node configuration - // from the specified path - cfg, loadCfgErr = config.LoadConfigFile(c.nodeConfigPath) - } else { - // Load the default node configuration - cfg, loadCfgErr = config.LoadOrMakeConfigWithOptions(nodeDir) + // Initialize the logger + zapLogger, err := initializeLogger(io.Out(), c.logLevel, c.logFormat) + if err != nil { + return fmt.Errorf("unable to initialize zap logger, %w", err) } - if loadCfgErr != nil { - return fmt.Errorf("unable to load node configuration, %w", loadCfgErr) + defer func() { + // Sync the logger before exiting + _ = zapLogger.Sync() + }() + + // Wrap the zap logger + logger := log.ZapLoggerToSlog(zapLogger) + + if c.lazyInit { + if err := lazyInitNodeDir(io, nodeDir); err != nil { + return fmt.Errorf("unable to lazy-init the node directory, %w", err) + } } - // Initialize the log level - logLevel, err := zapcore.ParseLevel(c.logLevel) + // Load the configuration + cfg, err := config.LoadConfig(nodeDir) if err != nil { - return fmt.Errorf("unable to parse log level, %w", err) + return fmt.Errorf("%s, %w", tryConfigInit, err) } - // Initialize the log format - logFormat := log.Format(strings.ToLower(c.logFormat)) + // Check if the genesis.json exists + if !osm.FileExists(genesisPath) { + if !c.lazyInit { + return errMissingGenesis + } - // Initialize the zap logger - zapLogger := log.GetZapLoggerFn(logFormat)(io.Out(), logLevel) + // Load the private validator secrets + privateKey := privval.LoadFilePV( + cfg.PrivValidatorKeyFile(), + cfg.PrivValidatorStateFile(), + ) - // Wrap the zap logger - logger := log.ZapLoggerToSlog(zapLogger) + // Init a new genesis.json + if err := lazyInitGenesis(io, c, genesisPath, privateKey.GetPubKey()); err != nil { + return fmt.Errorf("unable to initialize genesis.json, %w", err) + } + } // Initialize telemetry - telemetry.Init(*cfg.Telemetry) + if err := telemetry.Init(*cfg.Telemetry); err != nil { + return fmt.Errorf("unable to initialize telemetry, %w", err) + } - // Write genesis file if missing. - // NOTE: this will be dropped in a PR that resolves issue #1886: - // https://github.com/gnolang/gno/issues/1886 - if !osm.FileExists(genesisPath) { - // Create priv validator first. - // Need it to generate genesis.json - newPrivValKey := cfg.PrivValidatorKeyFile() - newPrivValState := cfg.PrivValidatorStateFile() - priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) - pk := priv.GetPubKey() - - // Generate genesis.json file - if err := generateGenesisFile(genesisPath, pk, c); err != nil { - return fmt.Errorf("unable to generate genesis file: %w", err) - } + // Print the starting graphic + if c.logFormat != string(log.JSONFormat) { + io.Println(startGraphic) } - // Initialize the indexer config - txEventStoreCfg, err := getTxEventStoreConfig(c) + // Create a top-level shared event switch + evsw := events.NewEventSwitch() + + // Create application and node + cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger) if err != nil { - return fmt.Errorf("unable to parse indexer config, %w", err) + return fmt.Errorf("unable to create the Gnoland app, %w", err) } - cfg.TxEventStore = txEventStoreCfg - // Create application and node. - gnoApp, err := gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) + // Create a default node, with the given setup + gnoNode, err := node.DefaultNewNode(cfg, genesisPath, evsw, logger) if err != nil { - return fmt.Errorf("error in creating new app: %w", err) + return fmt.Errorf("unable to create the Gnoland node, %w", err) } - cfg.LocalApp = gnoApp - if logFormat != log.JSONFormat { - io.Println(startGraphic) + // Start the node (async) + if err := gnoNode.Start(); err != nil { + return fmt.Errorf("unable to start the Gnoland node, %w", err) } - gnoNode, err := node.DefaultNewNode(cfg, genesisPath, logger) - if err != nil { - return fmt.Errorf("error in creating node: %w", err) - } + // Set up the wait context + nodeCtx, _ := signal.NotifyContext( + ctx, + os.Interrupt, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) - if c.skipStart { - io.ErrPrintln("'--skip-start' is set. Exiting.") + // Wait for the exit signal + <-nodeCtx.Done() + + if !gnoNode.IsRunning() { return nil } - if err := gnoNode.Start(); err != nil { - return fmt.Errorf("error in start node: %w", err) + // Gracefully stop the gno node + if err := gnoNode.Stop(); err != nil { + return fmt.Errorf("unable to gracefully stop the Gnoland node, %w", err) } - osm.TrapSignal(func() { - if gnoNode.IsRunning() { - _ = gnoNode.Stop() + return nil +} + +// lazyInitNodeDir initializes new secrets, and a default configuration +// in the given node directory, if not present +func lazyInitNodeDir(io commands.IO, nodeDir string) error { + var ( + configPath = constructConfigPath(nodeDir) + secretsPath = constructSecretsPath(nodeDir) + ) + + // Check if the configuration already exists + if !osm.FileExists(configPath) { + // Create the gnoland config options + cfg := &configInitCfg{ + configCfg: configCfg{ + configPath: constructConfigPath(nodeDir), + }, } - // Sync the logger before exiting - _ = zapLogger.Sync() - }) + // Run gnoland config init + if err := execConfigInit(cfg, io); err != nil { + return fmt.Errorf("unable to initialize config, %w", err) + } + + io.Printfln("WARN: Initialized default node config at %q", filepath.Dir(cfg.configPath)) + io.Println() + } + + // Create the gnoland secrets options + secrets := &secretsInitCfg{ + commonAllCfg: commonAllCfg{ + dataDir: secretsPath, + }, + forceOverwrite: false, // existing secrets shouldn't be pruned + } - // Run forever - select {} + // Run gnoland secrets init + err := execSecretsInit(secrets, []string{}, io) + if err == nil { + io.Printfln("WARN: Initialized default node secrets at %q", secrets.dataDir) + + return nil + } + + // Check if the error is valid + if errors.Is(err, errOverwriteNotEnabled) { + // No new secrets were generated + return nil + } + + return fmt.Errorf("unable to initialize secrets, %w", err) +} + +// lazyInitGenesis a new genesis.json file, with a signle validator +func lazyInitGenesis( + io commands.IO, + c *startCfg, + genesisPath string, + publicKey crypto.PubKey, +) error { + // Check if the genesis.json is present + if osm.FileExists(genesisPath) { + return nil + } + + // Generate the new genesis.json file + if err := generateGenesisFile(genesisPath, publicKey, c); err != nil { + return fmt.Errorf("unable to generate genesis file, %w", err) + } + + io.Printfln("WARN: Initialized genesis.json at %q", genesisPath) + + return nil +} + +// initializeLogger initializes the zap logger using the given format and log level, +// outputting to the given IO +func initializeLogger(io io.WriteCloser, logLevel, logFormat string) (*zap.Logger, error) { + // Initialize the log level + level, err := zapcore.ParseLevel(logLevel) + if err != nil { + return nil, fmt.Errorf("unable to parse log level, %w", err) + } + + // Initialize the log format + format := log.Format(strings.ToLower(logFormat)) + + // Initialize the zap logger + return log.GetZapLoggerFn(format)(io, level), nil } func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error { @@ -343,9 +404,7 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - test1 := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, test1, defaultFee, nil) + pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, genesisDeployAddress, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) } @@ -371,27 +430,3 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro return nil } - -// getTxEventStoreConfig constructs an event store config from provided user options -func getTxEventStoreConfig(c *startCfg) (*eventstorecfg.Config, error) { - var cfg *eventstorecfg.Config - - switch c.txEventStoreType { - case file.EventStoreType: - if c.txEventStorePath == "" { - return nil, errors.New("unspecified file transaction indexer path") - } - - // Fill out the configuration - cfg = &eventstorecfg.Config{ - EventStoreType: file.EventStoreType, - Params: map[string]any{ - file.Path: c.txEventStorePath, - }, - } - default: - cfg = eventstorecfg.DefaultEventStoreConfig() - } - - return cfg, nil -} diff --git a/gno.land/cmd/gnoland/start_test.go b/gno.land/cmd/gnoland/start_test.go index cdec6de0f99..2f620fcd360 100644 --- a/gno.land/cmd/gnoland/start_test.go +++ b/gno.land/cmd/gnoland/start_test.go @@ -4,38 +4,110 @@ import ( "bytes" "context" "path/filepath" + "strings" "testing" "time" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) -func TestStartInitialize(t *testing.T) { - t.Parallel() +// retryUntilTimeout runs the callback until the timeout is exceeded, or +// the callback returns a flag indicating completion +func retryUntilTimeout(ctx context.Context, cb func() bool) error { + ch := make(chan error, 1) + + go func() { + defer close(ch) + + for { + select { + case <-ctx.Done(): + ch <- ctx.Err() + + return + default: + retry := cb() + if !retry { + ch <- nil + return + } + } + + time.Sleep(500 * time.Millisecond) + } + }() + + return <-ch +} + +// prepareNodeRPC sets the RPC listen address for the node to be an arbitrary +// free address. Setting the listen port to a free port on the machine avoids +// node collisions between different testing suites +func prepareNodeRPC(t *testing.T, nodeDir string) { + t.Helper() + + path := constructConfigPath(nodeDir) + args := []string{ + "config", + "init", + "--config-path", + path, + } + + // Prepare the IO + mockOut := new(bytes.Buffer) + mockErr := new(bytes.Buffer) + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOut)) + io.SetErr(commands.WriteNopCloser(mockErr)) + + // Prepare the cmd context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() - // NOTE: cannot be txtar tests as they use their own parsing for the - // "gnoland" command line. See pkg/integration. + // Run config init + require.NoError(t, newRootCmd(io).ParseAndRun(ctx, args)) + + args = []string{ + "config", + "set", + "--config-path", + path, + "rpc.laddr", + "tcp://0.0.0.0:0", + } + + // Run config set + require.NoError(t, newRootCmd(io).ParseAndRun(ctx, args)) +} + +func TestStart_Lazy(t *testing.T) { + t.Parallel() var ( nodeDir = t.TempDir() genesisFile = filepath.Join(nodeDir, "test_genesis.json") - - args = []string{ - "start", - "--skip-start", - "--skip-failing-genesis-txs", - - // These two flags are tested together as they would otherwise - // pollute this directory (cmd/gnoland) if not set. - "--data-dir", - nodeDir, - "--genesis", - genesisFile, - } ) + // Prepare the config + prepareNodeRPC(t, nodeDir) + + args := []string{ + "start", + "--lazy", + "--skip-failing-genesis-txs", + + // These two flags are tested together as they would otherwise + // pollute this directory (cmd/gnoland) if not set. + "--data-dir", + nodeDir, + "--genesis", + genesisFile, + } + // Prepare the IO mockOut := new(bytes.Buffer) mockErr := new(bytes.Buffer) @@ -44,13 +116,49 @@ func TestStartInitialize(t *testing.T) { io.SetErr(commands.WriteNopCloser(mockErr)) // Create and run the command - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) defer cancelFn() - cmd := newRootCmd(io) - require.NoError(t, cmd.ParseAndRun(ctx, args)) + // Set up the command ctx + g, gCtx := errgroup.WithContext(ctx) + + // Start the node + g.Go(func() error { + return newRootCmd(io).ParseAndRun(gCtx, args) + }) + + // Set up the retry ctx + retryCtx, retryCtxCancelFn := context.WithTimeout(ctx, 5*time.Second) + defer retryCtxCancelFn() + + // This is a very janky way to verify the node has started. + // The alternative is to poll the node's RPC endpoints, but for some reason + // this introduces a lot of flakyness to the testing suite -- shocking! + // In an effort to keep this simple, and avoid randomly failing tests, + // we query the CLI output of the command + require.NoError(t, retryUntilTimeout(retryCtx, func() bool { + return !strings.Contains(mockOut.String(), startGraphic) + })) + + cancelFn() // stop the node + require.NoError(t, g.Wait()) - // Make sure the directory is created - assert.DirExists(t, nodeDir) + // Make sure the genesis is generated assert.FileExists(t, genesisFile) + + // Make sure the config is generated (default) + assert.FileExists(t, constructConfigPath(nodeDir)) + + // Make sure the secrets are generated + var ( + secretsPath = constructSecretsPath(nodeDir) + validatorKeyPath = filepath.Join(secretsPath, defaultValidatorKeyName) + validatorStatePath = filepath.Join(secretsPath, defaultValidatorStateName) + nodeKeyPath = filepath.Join(secretsPath, defaultNodeKeyName) + ) + + assert.DirExists(t, secretsPath) + assert.FileExists(t, validatorKeyPath) + assert.FileExists(t, validatorStatePath) + assert.FileExists(t, nodeKeyPath) } diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index e62bce0c59f..6249d2ff7a0 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -16,6 +16,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: \d+' stdout 'HEIGHT: \d+' stdout 'EVENTS: \[\]' +stdout 'TX HASH: ' -- hello.gno -- package hello diff --git a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar index 5cfd48bf2ea..bcec784a530 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar @@ -7,7 +7,7 @@ gnoland start ! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 # check error message -! stdout .+ +stdout 'TX HASH: ' stderr 'as string value in return statement' stderr '"std" imported and not used' diff --git a/gno.land/cmd/gnoland/testdata/addpkg_namespace.txtar b/gno.land/cmd/gnoland/testdata/addpkg_namespace.txtar new file mode 100644 index 00000000000..5a88fd6d603 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/addpkg_namespace.txtar @@ -0,0 +1,88 @@ +loadpkg gno.land/r/demo/users +loadpkg gno.land/r/sys/users + +adduser admin +adduser gui + +patchpkg "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" $USER_ADDR_admin # 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 2000000 -broadcast -chainid tendermint_test gui +stdout 'OK!' +stdout 'false' + +# Gui should be able to addpkg on test1 addr +# gui addpkg -> gno.land/r//mysuperpkg +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test gui +stdout 'OK!' + +# Gui should be able to addpkg on random name +# gui addpkg -> gno.land/r/randomname/mysuperpkg +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/randomname/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 100000000 -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 2000000 -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 2000000 -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/$USER_ADDR_test1/one -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test gui +stderr 'unauthorized user' + +# 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/$USER_ADDR_gui/one -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test gui +stdout 'OK!' + +## Test unregistered namespace + +# Call addpkg with admin user on gui namespace +# admin addpkg -> gno.land/r/guiland/one +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test admin +stderr 'unauthorized user' + +## 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 2000000 -broadcast -chainid=tendermint_test -args $USER_ADDR_gui 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 2000000 -broadcast -chainid=tendermint_test -args $USER_ADDR_admin -args 'guiland' -args 'im gui' 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 100000000 -broadcast -chainid=tendermint_test gui +stdout 'OK!' + +# Test admin publishing on guiland/two +# admin addpkg -> gno.land/r/guiland/two +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/two -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test admin +stderr 'unauthorized user' + +-- one.gno -- +package one + +func Render(path string) string { + return "# Hello One" +} diff --git a/gno.land/cmd/gnoland/testdata/assertorigincall.txtar b/gno.land/cmd/gnoland/testdata/assertorigincall.txtar new file mode 100644 index 00000000000..e3cd1be744a --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/assertorigincall.txtar @@ -0,0 +1,247 @@ +# This test ensures the consistency of the std.AssertOriginCall function, in +# the following situations: +# +# | Num | Msg Type | Call from | Entry Point | Result | +# |-----|:--------:|:-------------------:|:----------------------:|:------:| +# | 1 | MsgCall | wallet direct | myrealm.A() | PANIC | +# | 2 | | | myrealm.B() | pass | +# | 3 | | | myrealm.C() | pass | +# | 4 | | through /r/foo | myrealm.A() | PANIC | +# | 5 | | | myrealm.B() | pass | +# | 6 | | | myrealm.C() | PANIC | +# | 7 | | through /p/demo/bar | myrealm.A() | PANIC | +# | 8 | | | myrealm.B() | pass | +# | 9 | | | myrealm.C() | PANIC | +# | 10 | MsgRun | wallet direct | myrealm.A() | PANIC | +# | 11 | | | myrealm.B() | pass | +# | 12 | | | myrealm.C() | PANIC | +# | 13 | | through /r/foo | myrealm.A() | PANIC | +# | 14 | | | myrealm.B() | pass | +# | 15 | | | myrealm.C() | PANIC | +# | 16 | | through /p/demo/bar | myrealm.A() | PANIC | +# | 17 | | | myrealm.B() | pass | +# | 18 | | | myrealm.C() | PANIC | +# | 19 | MsgCall | wallet direct | std.AssertOriginCall() | pass | +# | 20 | MsgRun | wallet direct | std.AssertOriginCall() | PANIC | + +# Init +## set up and start a new node +loadpkg gno.land/r/myrlm $WORK/r/myrlm +loadpkg gno.land/r/foo $WORK/r/foo +loadpkg gno.land/p/demo/bar $WORK/p/demo/bar +gnoland start + +# Test cases +## 1. MsgCall -> myrlm.A: PANIC +! gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 2000000 -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 2000000 -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 2000000 -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 2000000 -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 2000000 -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 2000000 -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 -> myrlm.A: PANIC +## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +## stderr 'invalid non-origin call' + +## 8. MsgCall -> p/demo/bar.B -> myrlm.B: PASS +## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +## stdout 'OK!' + +## 9. MsgCall -> p/demo/bar.C -> myrlm.C: PANIC +## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 2000000 -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 2000000 -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 2000000 -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 2000000 -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 2000000 -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 2000000 -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 2000000 -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 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno +stderr 'invalid non-origin call' + +## 17. MsgRun -> run.main -> bar.B: PASS +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno +stdout 'OK!' + +## 18. MsgRun -> run.main -> bar.C: PANIC +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -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 2000000 -broadcast -chainid tendermint_test test1 +## stdout 'OK!' + +## 20. MsgRun -> std.AssertOriginCall: PANIC +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno +stderr 'invalid non-origin call' + + +-- r/myrlm/rlm.gno -- +package myrlm + +import "std" + +func A() { + C() +} + +func B() { + if false { + C() + } +} + +func C() { + std.AssertOriginCall() +} +-- r/foo/foo.gno -- +package foo + +import "gno.land/r/myrlm" + +func A() { + myrlm.A() +} + +func B() { + myrlm.B() +} + +func C() { + myrlm.C() +} +-- p/demo/bar/bar.gno -- +package bar + +import "gno.land/r/myrlm" + +func A() { + myrlm.A() +} + +func B() { + myrlm.B() +} + +func C() { + myrlm.C() +} +-- run/myrlmA.gno -- +package main + +import myrlm "gno.land/r/myrlm" + +func main() { + myrlm.A() +} +-- run/myrlmB.gno -- +package main + +import "gno.land/r/myrlm" + +func main() { + myrlm.B() +} +-- run/myrlmC.gno -- +package main + +import "gno.land/r/myrlm" + +func main() { + myrlm.C() +} +-- run/fooA.gno -- +package main + +import "gno.land/r/foo" + +func main() { + foo.A() +} +-- run/fooB.gno -- +package main + +import "gno.land/r/foo" + +func main() { + foo.B() +} +-- run/fooC.gno -- +package main + +import "gno.land/r/foo" + +func main() { + foo.C() +} +-- run/barA.gno -- +package main + +import "gno.land/p/demo/bar" + +func main() { + bar.A() +} +-- run/barB.gno -- +package main + +import "gno.land/p/demo/bar" + +func main() { + bar.B() +} +-- run/barC.gno -- +package main + +import "gno.land/p/demo/bar" + +func main() { + bar.C() +} +-- run/baz.gno -- +package main + +import "std" + +func main() { + std.AssertOriginCall() +} diff --git a/gno.land/cmd/gnoland/testdata/event_callback.txtar b/gno.land/cmd/gnoland/testdata/event_callback.txtar index a0366df1346..86377dfee6b 100644 --- a/gno.land/cmd/gnoland/testdata/event_callback.txtar +++ b/gno.land/cmd/gnoland/testdata/event_callback.txtar @@ -9,8 +9,8 @@ stdout OK! stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' -stdout 'EVENTS: \[{\"type\":\"foo\",\"pkg_path\":\"gno\.land\/r\/demo\/cbee\",\"func\":\"subFoo\",\"attrs\":\[{\"key\":\"k1\",\"value\":\"v1\"},{\"key\":\"k2\",\"value\":\"v2\"}\]},{\"type\":\"bar\",\"pkg_path\":\"gno\.land\/r\/demo\/cbee\",\"func\":\"subBar\",\"attrs\":\[{\"key\":\"bar\",\"value\":\"baz\"}\]}\]' - +stdout 'EVENTS: \[{\"type\":\"foo\",\"attrs\":\[{\"key\":\"k1\",\"value\":\"v1\"},{\"key\":\"k2\",\"value\":\"v2\"}],\"pkg_path\":\"gno.land\/r\/demo\/cbee\",\"func\":\"subFoo\"},{\"type\":\"bar\",\"attrs\":\[{\"key\":\"bar\",\"value\":\"baz\"}],\"pkg_path\":\"gno.land\/r\/demo\/cbee\",\"func\":\"subBar\"}]' +stdout 'TX HASH: ' -- cbee.gno -- package cbee diff --git a/gno.land/cmd/gnoland/testdata/event_defer_callback_loop.txtar b/gno.land/cmd/gnoland/testdata/event_defer_callback_loop.txtar index 9e7fac9abfe..44388be502d 100644 --- a/gno.land/cmd/gnoland/testdata/event_defer_callback_loop.txtar +++ b/gno.land/cmd/gnoland/testdata/event_defer_callback_loop.txtar @@ -9,7 +9,8 @@ stdout OK! stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' -stdout 'EVENTS: \[{\"type\":\"ForLoopEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\",\"attrs\":\[{\"key\":\"iteration\",\"value\":\"0\"},{\"key\":\"key\",\"value\":\"value\"}\]},{\"type\":\"ForLoopEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\",\"attrs\":\[{\"key\":\"iteration\",\"value\":\"1\"},{\"key\":\"key\",\"value\":\"value\"}\]},{\"type\":\"ForLoopEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\",\"attrs\":\[{\"key\":\"iteration\",\"value\":\"2\"},{\"key\":\"key\",\"value\":\"value\"}\]},{\"type\":\"ForLoopCompletionEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"forLoopEmitExample\",\"attrs\":\[{\"key\":\"count\",\"value\":\"3\"}\]},{\"type\":\"CallbackEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\",\"attrs\":\[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"}\]},{\"type\":\"CallbackCompletionEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"callbackEmitExample\",\"attrs\":\[{\"key\":\"key\",\"value\":\"value\"}\]},{\"type\":\"DeferEvent\",\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"deferEmitExample\",\"attrs\":\[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"}\]}\]' +stdout 'EVENTS: \[{\"type\":\"ForLoopEvent\",\"attrs\":\[{\"key\":\"iteration\",\"value\":\"0\"},{\"key\":\"key\",\"value\":\"value\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\"},{\"type\":\"ForLoopEvent\",\"attrs\":\[{\"key\":\"iteration\",\"value\":\"1\"},{\"key\":\"key\",\"value\":\"value\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\"},{\"type\":\"ForLoopEvent\",\"attrs\":\[{\"key\":\"iteration\",\"value\":\"2\"},{\"key\":\"key\",\"value\":\"value\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\"},{\"type\":\"ForLoopCompletionEvent\",\"attrs\":\[{\"key\":\"count\",\"value\":\"3\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"forLoopEmitExample\"},{\"type\":\"CallbackEvent\",\"attrs\":\[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"\"},{\"type\":\"CallbackCompletionEvent\",\"attrs\":\[{\"key\":\"key\",\"value\":\"value\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"callbackEmitExample\"},{\"type\":\"DeferEvent\",\"attrs\":\[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"}\],\"pkg_path\":\"gno.land\/r\/demo\/edcl\",\"func\":\"deferEmitExample\"}\]' +stdout 'TX HASH: ' -- edcl.gno -- diff --git a/gno.land/cmd/gnoland/testdata/event_for_statement.txtar b/gno.land/cmd/gnoland/testdata/event_for_statement.txtar index a05a614f985..be7f501f255 100644 --- a/gno.land/cmd/gnoland/testdata/event_for_statement.txtar +++ b/gno.land/cmd/gnoland/testdata/event_for_statement.txtar @@ -9,15 +9,16 @@ stdout OK! stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' -stdout 'EVENTS: \[{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]},{"type":"testing","pkg_path":"gno.land\/r\/demo\/foree","func":"Foo","attrs":\[{"key":"foo","value":"bar"}\]}\]' +stdout 'EVENTS: \[{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"},{\"type\":\"testing\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"Foo\"}\]' +stdout 'TX HASH: ' gnokey maketx call -pkgpath gno.land/r/demo/foree -func Bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' -stdout 'EVENTS: \[{"type":"Foo","pkg_path":"gno.land\/r\/demo\/foree","func":"subFoo","attrs":\[{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}\]},{"type":"Bar","pkg_path":"gno.land\/r\/demo\/foree","func":"subBar","attrs":\[{"key":"bar","value":"baz"}\]},{"type":"Foo","pkg_path":"gno.land\/r\/demo\/foree","func":"subFoo","attrs":\[{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}\]},{"type":"Bar","pkg_path":"gno.land\/r\/demo\/foree","func":"subBar","attrs":\[{"key":"bar","value":"baz"}\]}\]' - +stdout 'EVENTS: \[{\"type\":\"Foo\",\"attrs\":\[{\"key\":\"k1\",\"value\":\"v1\"},{\"key\":\"k2\",\"value\":\"v2\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"subFoo\"},{\"type\":\"Bar\",\"attrs\":\[{\"key\":\"bar\",\"value\":\"baz\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"subBar\"},{\"type\":\"Foo\",\"attrs\":\[{\"key\":\"k1\",\"value\":\"v1\"},{\"key\":\"k2\",\"value\":\"v2\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"subFoo\"},{\"type\":\"Bar\",\"attrs\":\[{\"key\":\"bar\",\"value\":\"baz\"}\],\"pkg_path\":\"gno.land\/r\/demo\/foree\",\"func\":\"subBar\"}\]' +stdout 'TX HASH: ' -- foree.gno -- diff --git a/gno.land/cmd/gnoland/testdata/event_multi_msg.txtar b/gno.land/cmd/gnoland/testdata/event_multi_msg.txtar new file mode 100644 index 00000000000..84afe3cc6a4 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/event_multi_msg.txtar @@ -0,0 +1,50 @@ +# load the package from $WORK directory +loadpkg gno.land/r/demo/simple_event $WORK/event + +# start a new node +gnoland start + +## test1 account should be available on default +gnokey query auth/accounts/${USER_ADDR_test1} +stdout 'height: 0' +stdout 'data: {' +stdout ' "BaseAccount": {' +stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "coins": "[0-9]*ugnot",' # dynamic +stdout ' "public_key": null,' +stdout ' "account_number": "0",' +stdout ' "sequence": "0"' +stdout ' }' +stdout '}' +! stderr '.+' # empty + + +## sign +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 0 -account-sequence 0 test1 +stdout 'Tx successfully signed and saved to ' + +## broadcast +gnokey broadcast $WORK/multi/multi_msg.tx -quiet=false + +stdout OK! +stdout 'GAS WANTED: 2000000' +stdout 'GAS USED: [0-9]+' +stdout 'HEIGHT: [0-9]+' +stdout 'EVENTS: \[{\"type\":\"TAG\",\"attrs\":\[{\"key\":\"KEY\",\"value\":\"value11\"}\],\"pkg_path\":\"gno.land\/r\/demo\/simple_event\",\"func\":\"Event\"},{\"type\":\"TAG\",\"attrs\":\[{\"key\":\"KEY\",\"value\":\"value22\"}\],\"pkg_path\":\"gno.land\/r\/demo\/simple_event\",\"func\":\"Event\"}\]' + + + +-- event/simple_event.gno -- +package simple_event + +import ( + "std" +) + +func Event(value string) { + std.Emit("TAG", "KEY", value) +} + +-- multi/multi_msg.tx -- +{"msg":[{"@type":"/vm.m_call","caller":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} + diff --git a/gno.land/cmd/gnoland/testdata/event.txtar b/gno.land/cmd/gnoland/testdata/event_normal.txtar similarity index 63% rename from gno.land/cmd/gnoland/testdata/event.txtar rename to gno.land/cmd/gnoland/testdata/event_normal.txtar index 45f2ceaf772..b2d6c01f3ec 100644 --- a/gno.land/cmd/gnoland/testdata/event.txtar +++ b/gno.land/cmd/gnoland/testdata/event_normal.txtar @@ -9,14 +9,16 @@ stdout OK! stdout 'GAS WANTED: 2000000' stdout 'GAS USED: \d+' stdout 'HEIGHT: \d+' -stdout 'EVENTS: \[{\"type\":\"foo\",\"pkg_path\":\"gno.land\/r\/demo\/ee\",\"func\":\"SubFoo\",\"attrs\":\[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"},{\"key\":\"key3\",\"value\":\"value3\"}\]},{\"type\":\"bar\",\"pkg_path\":\"gno.land\/r\/demo\/ee\",\"func\":\"SubBar\",\"attrs\":\[{\"key\":\"bar\",\"value\":\"baz\"}\]}\]' +stdout 'EVENTS: \[{\"type\":\"foo\",\"attrs\":\[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"},{\"key\":\"key3\",\"value\":\"value3\"}\],\"pkg_path\":\"gno.land\/r\/demo\/ee\",\"func\":\"SubFoo\"},{\"type\":\"bar\",\"attrs\":\[{\"key\":\"bar\",\"value\":\"baz\"}\],\"pkg_path\":\"gno.land\/r\/demo\/ee\",\"func\":\"SubBar\"}\]' +stdout 'TX HASH: ' gnokey maketx call -pkgpath gno.land/r/demo/ee -func Bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout 'GAS WANTED: 2000000' stdout 'GAS USED: \d+' stdout 'HEIGHT: \d+' -stdout 'EVENTS: \[{\"type\":\"bar\",\"pkg_path\":\"gno.land\/r\/demo\/ee\",\"func\":\"Bar\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\]}\]' +stdout 'EVENTS: \[{\"type\":\"bar\",\"attrs\":\[{\"key\":\"foo\",\"value\":\"bar\"}\],\"pkg_path\":\"gno.land/r/demo/ee\",\"func\":\"Bar\"}\]' +stdout 'TX HASH: ' -- ee.gno -- package ee diff --git a/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar index dab238a6122..8db2c7302fc 100644 --- a/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar +++ b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar @@ -30,19 +30,19 @@ stdout '"sequence": "1"' gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "2"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate only gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "2"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate skip gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "3"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # attempt calling hello.Grumpy (always panics). @@ -52,19 +52,19 @@ stdout 'Hello, George!' ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "3"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate only ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "3"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate skip ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "4"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' -- test/test.gno -- diff --git a/gno.land/cmd/gnoland/testdata/gnoweb_airgapped.txtar b/gno.land/cmd/gnoland/testdata/gnoweb_airgapped.txtar new file mode 100644 index 00000000000..3ed35a1b1d3 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/gnoweb_airgapped.txtar @@ -0,0 +1,38 @@ +# This test ensures that the "full security with airgap" commands, on gnoweb's +# help page, work as intended. + +# load the package from $WORK directory +loadpkg gno.land/r/demo/echo + +# start the node +gnoland start + +# Query account +gnokey query auth/accounts/${USER_ADDR_test1} +stdout 'height: 0' +stdout 'data: {' +stdout ' "BaseAccount": {' +stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "coins": "[0-9]*ugnot",' # dynamic +stdout ' "public_key": null,' +stdout ' "account_number": "0",' +stdout ' "sequence": "0"' +stdout ' }' +stdout '}' +! stderr '.+' # empty + +# Create transaction +gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -args "HELLO" test1 +cp stdout call.tx + +# Sign +gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 0 -account-sequence 0 test1 +cmpenv stdout sign.stdout.golden + +gnokey broadcast $WORK/call.tx +stdout '("HELLO" string)' +stdout 'GAS WANTED: 2000000' + +-- sign.stdout.golden -- + +Tx successfully signed and saved to $WORK/call.tx diff --git a/gno.land/cmd/gnoland/testdata/issue_1786.txtar b/gno.land/cmd/gnoland/testdata/issue_1786.txtar index 44ea17674c9..7c92e81dfb6 100644 --- a/gno.land/cmd/gnoland/testdata/issue_1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_1786.txtar @@ -5,7 +5,7 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 6000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` diff --git a/gno.land/cmd/gnoland/testdata/issue_2283.txtar b/gno.land/cmd/gnoland/testdata/issue_2283.txtar new file mode 100644 index 00000000000..653a4dd79b0 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/issue_2283.txtar @@ -0,0 +1,111 @@ +# Test for https://github.com/gnolang/gno/issues/2283 + +# 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/r/demo/imports $WORK/imports + +gnoland start + +! gnokey broadcast $WORK/add_feeds.tx + +gnokey maketx addpkg -pkgdir $WORK/bye -pkgpath gno.land/r/demo/bye -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +-- imports/imports.gno -- +// Handles "implicit" imports before running the main failing transaction sequence. +package imports + +import ( + _ "encoding/binary" +) + +-- add_feeds.tx -- +{ + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "Name": "social_feeds", + "Path": "gno.land/r/demo/teritori/social_feeds", + "Files": [ + { + "Name": "binutils_extra.gno", + "Body": "package social_feeds\n\nimport (\n\t\"encoding/binary\"\n)\n\nfunc EncodeLengthPrefixedStringUint32BE(s string) []byte {\n\tb := make([]byte, 4+len(s))\n\tbinary.BigEndian.PutUint32(b, uint32(len(s)))\n\tcopy(b[4:], s)\n\treturn b\n}\n" + }, + { + "Name": "feed.gno", + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype FeedID uint64\n\nfunc (fid FeedID) String() string {\n\treturn strconv.Itoa(int(fid))\n}\n\nfunc (fid *FeedID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*fid = FeedID(val)\n}\n\nfunc (fid FeedID) ToJSON() string {\n\treturn strconv.Itoa(int(fid))\n}\n\ntype Feed struct {\n\tid FeedID\n\turl string\n\tname string\n\tcreator std.Address\n\towner std.Address\n\tposts avl.Tree // pidkey -> *Post\n\tcreatedAt int64\n\n\tflags *flags_index.FlagsIndex\n\thiddenPostsByUser avl.Tree // std.Address => *avl.Tree (postID => bool)\n\n\tpostsCtr uint64\n}\n\nfunc newFeed(fid FeedID, url string, name string, creator std.Address) *Feed {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid feed name: \" + name)\n\t}\n\n\tif gFeedsByName.Has(name) {\n\t\tpanic(\"feed already exists: \" + name)\n\t}\n\n\treturn &Feed{\n\t\tid: fid,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\towner: creator,\n\t\tposts: avl.Tree{},\n\t\tcreatedAt: time.Now().Unix(),\n\t\tflags: flags_index.NewFlagsIndex(),\n\t\tpostsCtr: 0,\n\t}\n}\n\nfunc (feed *Feed) incGetPostID() PostID {\n\tfeed.postsCtr++\n\treturn PostID(feed.postsCtr)\n}\n\nfunc (feed *Feed) GetPost(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpost_, exists := feed.posts.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn post_.(*Post)\n}\n\nfunc (feed *Feed) MustGetPost(pid PostID) *Post {\n\tpost := feed.GetPost(pid)\n\tif post == nil {\n\t\tpanic(\"post does not exist\")\n\t}\n\treturn post\n}\n\nfunc (feed *Feed) AddPost(creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\tpid := feed.incGetPostID()\n\tpidkey := postIDKey(pid)\n\n\tpost := newPost(feed, pid, creator, parentID, category, metadata)\n\tfeed.posts.Set(pidkey, post)\n\n\t// If post is a comment then increase the comment count on parent\n\tif uint64(parentID) != 0 {\n\t\tparent := feed.MustGetPost(parentID)\n\t\tparent.commentsCount += 1\n\t}\n\n\treturn post\n}\n\nfunc (feed *Feed) FlagPost(flagBy std.Address, pid PostID) {\n\tflagID := getFlagID(feed.id, pid)\n\n\tif feed.flags.HasFlagged(flagID, flagBy.String()) {\n\t\tpanic(\"already flagged\")\n\t}\n\n\tfeed.flags.Flag(flagID, flagBy.String())\n}\n\nfunc (feed *Feed) BanPost(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := feed.posts.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (feed *Feed) HidePostForUser(caller std.Address, pid PostID) {\n\tuserAddr := caller.String()\n\n\tvalue, exists := feed.hiddenPostsByUser.Get(userAddr)\n\tvar hiddenPosts *avl.Tree\n\tif exists {\n\t\thiddenPosts = value.(*avl.Tree)\n\t} else {\n\t\thiddenPosts = avl.NewTree()\n\t\tfeed.hiddenPostsByUser.Set(userAddr, hiddenPosts)\n\t}\n\n\tif hiddenPosts.Has(pid.String()) {\n\t\tpanic(\"PostID is already hidden: \" + pid.String())\n\t}\n\n\thiddenPosts.Set(pid.String(), true)\n}\n\nfunc (feed *Feed) UnHidePostForUser(userAddress std.Address, pid PostID) {\n\tvalue, exists := feed.hiddenPostsByUser.Get(userAddress.String())\n\tvar hiddenPosts *avl.Tree\n\tif exists {\n\t\thiddenPosts = value.(*avl.Tree)\n\t\t_, removed := hiddenPosts.Remove(pid.String())\n\t\tif !removed {\n\t\t\tpanic(\"Post is not hidden: \" + pid.String())\n\t\t}\n\t} else {\n\t\tpanic(\"User has not hidden post: \" + pid.String())\n\t}\n}\n\nfunc (feed *Feed) Render() string {\n\tpkgpath := std.CurrentRealmPath()\n\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"Feed: %s (ID: %s) - Owner: %s\", feed.name, feed.id, feed.owner)\n\tstr += \"\\n\\n There are \" + intToString(feed.posts.Size()) + \" post(s) \\n\\n\"\n\n\tif feed.posts.Size() > 0 {\n\t\tfeed.posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"\\n\"\n\t\t\t}\n\n\t\t\tpost := value.(*Post)\n\t\t\tpostUrl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + feed.name + \"/\" + post.id.String()\n\n\t\t\tstr += \" * [\" +\n\t\t\t\t\"PostID: \" + post.id.String() +\n\t\t\t\t\" - \" + intToString(post.reactions.Size()) + \" reactions \" +\n\t\t\t\t\" - \" + ufmt.Sprintf(\"%d\", post.tipAmount) + \" tip amount\" +\n\t\t\t\t\"]\" +\n\t\t\t\t\"(\" + postUrl + \")\" +\n\t\t\t\t\"\\n\"\n\t\t\treturn false\n\t\t})\n\n\t\tstr += \"-------------------------\\n\"\n\t\tstr += feed.flags.Dump()\n\t}\n\n\tstr += \"---------------------------------------\\n\"\n\tif feed.hiddenPostsByUser.Size() > 0 {\n\t\tstr += \"Hidden posts by users:\\n\\n\"\n\n\t\tfeed.hiddenPostsByUser.Iterate(\"\", \"\", func(userAddr string, value interface{}) bool {\n\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\tstr += \"\\nUser address: \" + userAddr + \"\\n\"\n\n\t\t\thiddenPosts.Iterate(\"\", \"\", func(pid string, value interface{}) bool {\n\t\t\t\tstr += \"- PostID: \" + pid + \"\\n\"\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\treturn false\n\t\t})\n\t}\n\n\treturn str\n}\n\nfunc (feed *Feed) ToJSON() string {\n\tposts := []ujson.FormatKV{}\n\tfeed.posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tposts = append(posts, ujson.FormatKV{\n\t\t\tKey: key,\n\t\t\tValue: value.(*Post),\n\t\t})\n\t\treturn false\n\t})\n\tfeedJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(feed.id)},\n\t\t{Key: \"url\", Value: feed.url},\n\t\t{Key: \"name\", Value: feed.name},\n\t\t{Key: \"creator\", Value: feed.creator},\n\t\t{Key: \"owner\", Value: feed.owner},\n\t\t{Key: \"posts\", Value: ujson.FormatObject(posts), Raw: true},\n\t\t{Key: \"createdAt\", Value: feed.createdAt},\n\t\t{Key: \"postsCtr\", Value: feed.postsCtr},\n\t\t// TODO: convert flags, hiddenPostsByUser\n\t\t// {Key: \"flags\", Value: feed.flags},\n\t\t// {Key: \"hiddenPostsByUser\", Value: feed.hiddenPostsByUser},\n\t})\n\treturn feedJSON\n}\n\nfunc (feed *Feed) 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\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tfeed.id = FeedID(fid)\n\t\t}},\n\t\t{Key: \"url\", Value: &feed.url},\n\t\t{Key: \"name\", Value: &feed.name},\n\t\t{Key: \"creator\", Value: &feed.creator},\n\t\t{Key: \"owner\", Value: &feed.owner},\n\t\t{Key: \"posts\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tposts := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\tpostNode := child.Value\n\n\t\t\t\tpost := Post{}\n\t\t\t\tpost.FromJSON(postNode.String())\n\t\t\t\tposts.Set(child.Key, &post)\n\t\t\t}\n\t\t\tfeed.posts = *posts\n\t\t}},\n\t\t{Key: \"createdAt\", Value: &feed.createdAt},\n\t\t{Key: \"postsCtr\", Value: &feed.postsCtr},\n\t})\n}\n" + }, + { + "Name": "feeds.gno", + "Body": "package social_feeds\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgFeeds avl.Tree // id -> *Feed\n\tgFeedsCtr int // increments Feed.id\n\tgFeedsByName avl.Tree // name -> *Feed\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n" + }, + { + "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" + }, + { + "Name": "flags.gno", + "Body": "package social_feeds\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/teritori/flags_index\"\n)\n\nvar SEPARATOR = \"/\"\n\nfunc getFlagID(fid FeedID, pid PostID) flags_index.FlagID {\n\treturn flags_index.FlagID(fid.String() + SEPARATOR + pid.String())\n}\n\nfunc parseFlagID(flagID flags_index.FlagID) (FeedID, PostID) {\n\tparts := strings.Split(string(flagID), SEPARATOR)\n\tif len(parts) != 2 {\n\t\tpanic(\"invalid flag ID '\" + string(flagID) + \"'\")\n\t}\n\tfid, err := strconv.Atoi(parts[0])\n\tif err != nil || fid == 0 {\n\t\tpanic(\"invalid feed ID in flag ID '\" + parts[0] + \"'\")\n\t}\n\tpid, err := strconv.Atoi(parts[1])\n\tif err != nil || pid == 0 {\n\t\tpanic(\"invalid post ID in flag ID '\" + parts[1] + \"'\")\n\t}\n\treturn FeedID(fid), PostID(pid)\n}\n" + }, + { + "Name": "gno.mod", + "Body": "module gno.land/r/demo/teritori/social_feeds\n\nrequire (\n\tgno.land/p/demo/avl v0.0.0-latest\n\tgno.land/p/demo/teritori/dao_interfaces v0.0.0-latest\n\tgno.land/p/demo/teritori/flags_index v0.0.0-latest\n\tgno.land/p/demo/teritori/ujson v0.0.0-latest\n\tgno.land/p/demo/testutils v0.0.0-latest\n\tgno.land/p/demo/ufmt v0.0.0-latest\n\tgno.land/r/demo/boards v0.0.0-latest\n\tgno.land/r/demo/users v0.0.0-latest\n)\n" + }, + { + "Name": "messages.gno", + "Body": "package social_feeds\n\nimport (\n\t\"strings\"\n\n\t\"gno.land/p/demo/teritori/dao_interfaces\"\n\t\"gno.land/p/demo/teritori/ujson\"\n)\n\nvar PKG_PATH = \"gno.land/r/demo/teritori/social_feeds\"\n\n// Ban a post\ntype ExecutableMessageBanPost struct {\n\tdao_interfaces.ExecutableMessage\n\n\tFeedID FeedID\n\tPostID PostID\n\tReason string\n}\n\nfunc (msg ExecutableMessageBanPost) Type() string {\n\treturn \"gno.land/r/demo/teritori/social_feeds.BanPost\"\n}\n\nfunc (msg *ExecutableMessageBanPost) ToJSON() string {\n\treturn ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"feedId\", Value: msg.FeedID},\n\t\t{Key: \"postId\", Value: msg.PostID},\n\t\t{Key: \"reason\", Value: msg.Reason},\n\t})\n}\n\nfunc (msg *ExecutableMessageBanPost) String() string {\n\tvar ss []string\n\tss = append(ss, msg.Type())\n\n\tfeed := getFeed(msg.FeedID)\n\ts := \"\"\n\n\tif feed != nil {\n\t\ts += \"Feed: \" + feed.name + \" (\" + feed.id.String() + \")\"\n\n\t\tpost := feed.GetPost(msg.PostID)\n\t\tif post != nil {\n\t\t\ts += \"\\n Post: \" + post.id.String()\n\t\t} else {\n\t\t\ts += \"\\n Post: \" + msg.PostID.String() + \" (not found)\"\n\t\t}\n\t} else {\n\t\ts += \"Feed: \" + msg.FeedID.String() + \" (not found)\"\n\t}\n\n\ts += \"\\nReason: \" + msg.Reason\n\n\tss = append(ss, s)\n\n\treturn strings.Join(ss, \"\\n---\\n\")\n}\n\ntype BanPostHandler struct {\n\tdao_interfaces.MessageHandler\n}\n\nfunc NewBanPostHandler() *BanPostHandler {\n\treturn &BanPostHandler{}\n}\n\nfunc (h *BanPostHandler) Execute(iMsg dao_interfaces.ExecutableMessage) {\n\tmsg := iMsg.(*ExecutableMessageBanPost)\n\tBanPost(msg.FeedID, msg.PostID, msg.Reason)\n}\n\nfunc (h BanPostHandler) Type() string {\n\treturn ExecutableMessageBanPost{}.Type()\n}\n\nfunc (h *BanPostHandler) MessageFromJSON(ast *ujson.JSONASTNode) dao_interfaces.ExecutableMessage {\n\tmsg := &ExecutableMessageBanPost{}\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"feedId\", Value: &msg.FeedID},\n\t\t{Key: \"postId\", Value: &msg.PostID},\n\t\t{Key: \"reason\", Value: &msg.Reason},\n\t})\n\treturn msg\n}\n" + }, + { + "Name": "misc.gno", + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc getFeed(fid FeedID) *Feed {\n\tfidkey := feedIDKey(fid)\n\tfeed_, exists := gFeeds.Get(fidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tfeed := feed_.(*Feed)\n\treturn feed\n}\n\nfunc mustGetFeed(fid FeedID) *Feed {\n\tfeed := getFeed(fid)\n\tif feed == nil {\n\t\tpanic(\"Feed does not exist\")\n\t}\n\treturn feed\n}\n\nfunc incGetFeedID() FeedID {\n\tgFeedsCtr++\n\treturn FeedID(gFeedsCtr)\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t} else {\n\t\treturn user.Name\n\t}\n}\n\nfunc feedIDKey(fid FeedID) string {\n\treturn padZero(uint64(fid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) >= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) >= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc bytesToString(b []byte) string {\n\treturn base64.RawURLEncoding.EncodeToString(b)\n}\n\nfunc intToString(val int) string {\n\treturn strconv.Itoa(val)\n}\n" + }, + { + "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" + }, + { + "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" + }, + { + "Name": "render.gno", + "Body": "package social_feeds\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc renderFeed(parts []string) string {\n\t// /r/demo/social_feeds_v4:FEED_NAME\n\tname := parts[0]\n\tfeedI, exists := gFeedsByName.Get(name)\n\tif !exists {\n\t\treturn \"feed does not exist: \" + name\n\t}\n\treturn feedI.(*Feed).Render()\n}\n\nfunc renderPost(parts []string) string {\n\t// /r/demo/boards:FEED_NAME/POST_ID\n\tname := parts[0]\n\tfeedI, exists := gFeedsByName.Get(name)\n\tif !exists {\n\t\treturn \"feed does not exist: \" + name\n\t}\n\tpid, err := strconv.Atoi(parts[1])\n\tif err != nil {\n\t\treturn \"invalid thread id: \" + parts[1]\n\t}\n\tfeed := feedI.(*Feed)\n\tpost := feed.MustGetPost(PostID(pid))\n\treturn post.Render()\n}\n\nfunc renderFeedsList() string {\n\tstr := \"There are \" + intToString(gFeeds.Size()) + \" available feeds:\\n\\n\"\n\tgFeeds.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tfeed := value.(*Feed)\n\t\tstr += \" * [\" + feed.url + \" (FeedID: \" + feed.id.String() + \")](\" + feed.url + \")\\n\"\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderFeedsList()\n\t}\n\n\tparts := strings.Split(path, \"/\")\n\n\tif len(parts) == 1 {\n\t\t// /r/demo/social_feeds_v4:FEED_NAME\n\t\treturn renderFeed(parts)\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/social_feeds_v4:FEED_NAME/POST_ID\n\t\treturn renderPost(parts)\n\t}\n\n\treturn \"Not found\"\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "100000000", + "gas_fee": "10000000ugnot" + }, + "signatures": [ + { + "pub_key": { + "@type": "/tm.PubKeySecp256k1", + "value": "A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y" + }, + "signature": "fg01rLWLymXHVn9fE9vNyo4i2idOAEJn6KsPnlMT5JdrWqjzLScI65JVpJJErQUQMdpx/LvBPNVG3Atv/VGekg==" + } + ], + "memo": "" +} + +-- bye/bye.gno -- +package bye + +import ( + "encoding/base64" +) + +func init() { + val, _ := base64.StdEncoding.DecodeString("heyhey") + println(val) + base64.StdEncoding.EncodeToString([]byte(val)) +} diff --git a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar new file mode 100644 index 00000000000..38b0c8fe865 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar @@ -0,0 +1,104 @@ +# Test for https://github.com/gnolang/gno/issues/2283 +# This is an earlier variant of the txtar which likely shares the same cause; +# but instead of causing an index out of range, it causes a "should not happen" +# panic from the store. + +# 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 + +gnoland start + +! gnokey broadcast $WORK/add_feeds.tx + +gnokey maketx addpkg -pkgdir $WORK/bye -pkgpath gno.land/r/demo/bye -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +-- add_feeds.tx -- +{ + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "Name": "social_feeds", + "Path": "gno.land/r/demo/teritori/social_feeds", + "Files": [ + { + "Name": "binutils_extra.gno", + "Body": "package social_feeds\n\nimport (\n\t\"encoding/binary\"\n)\n\nfunc EncodeLengthPrefixedStringUint32BE(s string) []byte {\n\tb := make([]byte, 4+len(s))\n\tbinary.BigEndian.PutUint32(b, uint32(len(s)))\n\tcopy(b[4:], s)\n\treturn b\n}\n" + }, + { + "Name": "feed.gno", + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype FeedID uint64\n\nfunc (fid FeedID) String() string {\n\treturn strconv.Itoa(int(fid))\n}\n\nfunc (fid *FeedID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*fid = FeedID(val)\n}\n\nfunc (fid FeedID) ToJSON() string {\n\treturn strconv.Itoa(int(fid))\n}\n\ntype Feed struct {\n\tid FeedID\n\turl string\n\tname string\n\tcreator std.Address\n\towner std.Address\n\tposts avl.Tree // pidkey -> *Post\n\tcreatedAt int64\n\n\tflags *flags_index.FlagsIndex\n\thiddenPostsByUser avl.Tree // std.Address => *avl.Tree (postID => bool)\n\n\tpostsCtr uint64\n}\n\nfunc newFeed(fid FeedID, url string, name string, creator std.Address) *Feed {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid feed name: \" + name)\n\t}\n\n\tif gFeedsByName.Has(name) {\n\t\tpanic(\"feed already exists: \" + name)\n\t}\n\n\treturn &Feed{\n\t\tid: fid,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\towner: creator,\n\t\tposts: avl.Tree{},\n\t\tcreatedAt: time.Now().Unix(),\n\t\tflags: flags_index.NewFlagsIndex(),\n\t\tpostsCtr: 0,\n\t}\n}\n\nfunc (feed *Feed) incGetPostID() PostID {\n\tfeed.postsCtr++\n\treturn PostID(feed.postsCtr)\n}\n\nfunc (feed *Feed) GetPost(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpost_, exists := feed.posts.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn post_.(*Post)\n}\n\nfunc (feed *Feed) MustGetPost(pid PostID) *Post {\n\tpost := feed.GetPost(pid)\n\tif post == nil {\n\t\tpanic(\"post does not exist\")\n\t}\n\treturn post\n}\n\nfunc (feed *Feed) AddPost(creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\tpid := feed.incGetPostID()\n\tpidkey := postIDKey(pid)\n\n\tpost := newPost(feed, pid, creator, parentID, category, metadata)\n\tfeed.posts.Set(pidkey, post)\n\n\t// If post is a comment then increase the comment count on parent\n\tif uint64(parentID) != 0 {\n\t\tparent := feed.MustGetPost(parentID)\n\t\tparent.commentsCount += 1\n\t}\n\n\treturn post\n}\n\nfunc (feed *Feed) FlagPost(flagBy std.Address, pid PostID) {\n\tflagID := getFlagID(feed.id, pid)\n\n\tif feed.flags.HasFlagged(flagID, flagBy.String()) {\n\t\tpanic(\"already flagged\")\n\t}\n\n\tfeed.flags.Flag(flagID, flagBy.String())\n}\n\nfunc (feed *Feed) BanPost(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := feed.posts.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (feed *Feed) HidePostForUser(caller std.Address, pid PostID) {\n\tuserAddr := caller.String()\n\n\tvalue, exists := feed.hiddenPostsByUser.Get(userAddr)\n\tvar hiddenPosts *avl.Tree\n\tif exists {\n\t\thiddenPosts = value.(*avl.Tree)\n\t} else {\n\t\thiddenPosts = avl.NewTree()\n\t\tfeed.hiddenPostsByUser.Set(userAddr, hiddenPosts)\n\t}\n\n\tif hiddenPosts.Has(pid.String()) {\n\t\tpanic(\"PostID is already hidden: \" + pid.String())\n\t}\n\n\thiddenPosts.Set(pid.String(), true)\n}\n\nfunc (feed *Feed) UnHidePostForUser(userAddress std.Address, pid PostID) {\n\tvalue, exists := feed.hiddenPostsByUser.Get(userAddress.String())\n\tvar hiddenPosts *avl.Tree\n\tif exists {\n\t\thiddenPosts = value.(*avl.Tree)\n\t\t_, removed := hiddenPosts.Remove(pid.String())\n\t\tif !removed {\n\t\t\tpanic(\"Post is not hidden: \" + pid.String())\n\t\t}\n\t} else {\n\t\tpanic(\"User has not hidden post: \" + pid.String())\n\t}\n}\n\nfunc (feed *Feed) Render() string {\n\tpkgpath := std.CurrentRealmPath()\n\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"Feed: %s (ID: %s) - Owner: %s\", feed.name, feed.id, feed.owner)\n\tstr += \"\\n\\n There are \" + intToString(feed.posts.Size()) + \" post(s) \\n\\n\"\n\n\tif feed.posts.Size() > 0 {\n\t\tfeed.posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"\\n\"\n\t\t\t}\n\n\t\t\tpost := value.(*Post)\n\t\t\tpostUrl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + feed.name + \"/\" + post.id.String()\n\n\t\t\tstr += \" * [\" +\n\t\t\t\t\"PostID: \" + post.id.String() +\n\t\t\t\t\" - \" + intToString(post.reactions.Size()) + \" reactions \" +\n\t\t\t\t\" - \" + ufmt.Sprintf(\"%d\", post.tipAmount) + \" tip amount\" +\n\t\t\t\t\"]\" +\n\t\t\t\t\"(\" + postUrl + \")\" +\n\t\t\t\t\"\\n\"\n\t\t\treturn false\n\t\t})\n\n\t\tstr += \"-------------------------\\n\"\n\t\tstr += feed.flags.Dump()\n\t}\n\n\tstr += \"---------------------------------------\\n\"\n\tif feed.hiddenPostsByUser.Size() > 0 {\n\t\tstr += \"Hidden posts by users:\\n\\n\"\n\n\t\tfeed.hiddenPostsByUser.Iterate(\"\", \"\", func(userAddr string, value interface{}) bool {\n\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\tstr += \"\\nUser address: \" + userAddr + \"\\n\"\n\n\t\t\thiddenPosts.Iterate(\"\", \"\", func(pid string, value interface{}) bool {\n\t\t\t\tstr += \"- PostID: \" + pid + \"\\n\"\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\treturn false\n\t\t})\n\t}\n\n\treturn str\n}\n\nfunc (feed *Feed) ToJSON() string {\n\tposts := []ujson.FormatKV{}\n\tfeed.posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tposts = append(posts, ujson.FormatKV{\n\t\t\tKey: key,\n\t\t\tValue: value.(*Post),\n\t\t})\n\t\treturn false\n\t})\n\tfeedJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(feed.id)},\n\t\t{Key: \"url\", Value: feed.url},\n\t\t{Key: \"name\", Value: feed.name},\n\t\t{Key: \"creator\", Value: feed.creator},\n\t\t{Key: \"owner\", Value: feed.owner},\n\t\t{Key: \"posts\", Value: ujson.FormatObject(posts), Raw: true},\n\t\t{Key: \"createdAt\", Value: feed.createdAt},\n\t\t{Key: \"postsCtr\", Value: feed.postsCtr},\n\t\t// TODO: convert flags, hiddenPostsByUser\n\t\t// {Key: \"flags\", Value: feed.flags},\n\t\t// {Key: \"hiddenPostsByUser\", Value: feed.hiddenPostsByUser},\n\t})\n\treturn feedJSON\n}\n\nfunc (feed *Feed) 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\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tfeed.id = FeedID(fid)\n\t\t}},\n\t\t{Key: \"url\", Value: &feed.url},\n\t\t{Key: \"name\", Value: &feed.name},\n\t\t{Key: \"creator\", Value: &feed.creator},\n\t\t{Key: \"owner\", Value: &feed.owner},\n\t\t{Key: \"posts\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tposts := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\tpostNode := child.Value\n\n\t\t\t\tpost := Post{}\n\t\t\t\tpost.FromJSON(postNode.String())\n\t\t\t\tposts.Set(child.Key, &post)\n\t\t\t}\n\t\t\tfeed.posts = *posts\n\t\t}},\n\t\t{Key: \"createdAt\", Value: &feed.createdAt},\n\t\t{Key: \"postsCtr\", Value: &feed.postsCtr},\n\t})\n}\n" + }, + { + "Name": "feeds.gno", + "Body": "package social_feeds\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgFeeds avl.Tree // id -> *Feed\n\tgFeedsCtr int // increments Feed.id\n\tgFeedsByName avl.Tree // name -> *Feed\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n" + }, + { + "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" + }, + { + "Name": "flags.gno", + "Body": "package social_feeds\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/teritori/flags_index\"\n)\n\nvar SEPARATOR = \"/\"\n\nfunc getFlagID(fid FeedID, pid PostID) flags_index.FlagID {\n\treturn flags_index.FlagID(fid.String() + SEPARATOR + pid.String())\n}\n\nfunc parseFlagID(flagID flags_index.FlagID) (FeedID, PostID) {\n\tparts := strings.Split(string(flagID), SEPARATOR)\n\tif len(parts) != 2 {\n\t\tpanic(\"invalid flag ID '\" + string(flagID) + \"'\")\n\t}\n\tfid, err := strconv.Atoi(parts[0])\n\tif err != nil || fid == 0 {\n\t\tpanic(\"invalid feed ID in flag ID '\" + parts[0] + \"'\")\n\t}\n\tpid, err := strconv.Atoi(parts[1])\n\tif err != nil || pid == 0 {\n\t\tpanic(\"invalid post ID in flag ID '\" + parts[1] + \"'\")\n\t}\n\treturn FeedID(fid), PostID(pid)\n}\n" + }, + { + "Name": "gno.mod", + "Body": "module gno.land/r/demo/teritori/social_feeds\n\nrequire (\n\tgno.land/p/demo/avl v0.0.0-latest\n\tgno.land/p/demo/teritori/dao_interfaces v0.0.0-latest\n\tgno.land/p/demo/teritori/flags_index v0.0.0-latest\n\tgno.land/p/demo/teritori/ujson v0.0.0-latest\n\tgno.land/p/demo/testutils v0.0.0-latest\n\tgno.land/p/demo/ufmt v0.0.0-latest\n\tgno.land/r/demo/boards v0.0.0-latest\n\tgno.land/r/demo/users v0.0.0-latest\n)\n" + }, + { + "Name": "messages.gno", + "Body": "package social_feeds\n\nimport (\n\t\"strings\"\n\n\t\"gno.land/p/demo/teritori/dao_interfaces\"\n\t\"gno.land/p/demo/teritori/ujson\"\n)\n\nvar PKG_PATH = \"gno.land/r/demo/teritori/social_feeds\"\n\n// Ban a post\ntype ExecutableMessageBanPost struct {\n\tdao_interfaces.ExecutableMessage\n\n\tFeedID FeedID\n\tPostID PostID\n\tReason string\n}\n\nfunc (msg ExecutableMessageBanPost) Type() string {\n\treturn \"gno.land/r/demo/teritori/social_feeds.BanPost\"\n}\n\nfunc (msg *ExecutableMessageBanPost) ToJSON() string {\n\treturn ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"feedId\", Value: msg.FeedID},\n\t\t{Key: \"postId\", Value: msg.PostID},\n\t\t{Key: \"reason\", Value: msg.Reason},\n\t})\n}\n\nfunc (msg *ExecutableMessageBanPost) String() string {\n\tvar ss []string\n\tss = append(ss, msg.Type())\n\n\tfeed := getFeed(msg.FeedID)\n\ts := \"\"\n\n\tif feed != nil {\n\t\ts += \"Feed: \" + feed.name + \" (\" + feed.id.String() + \")\"\n\n\t\tpost := feed.GetPost(msg.PostID)\n\t\tif post != nil {\n\t\t\ts += \"\\n Post: \" + post.id.String()\n\t\t} else {\n\t\t\ts += \"\\n Post: \" + msg.PostID.String() + \" (not found)\"\n\t\t}\n\t} else {\n\t\ts += \"Feed: \" + msg.FeedID.String() + \" (not found)\"\n\t}\n\n\ts += \"\\nReason: \" + msg.Reason\n\n\tss = append(ss, s)\n\n\treturn strings.Join(ss, \"\\n---\\n\")\n}\n\ntype BanPostHandler struct {\n\tdao_interfaces.MessageHandler\n}\n\nfunc NewBanPostHandler() *BanPostHandler {\n\treturn &BanPostHandler{}\n}\n\nfunc (h *BanPostHandler) Execute(iMsg dao_interfaces.ExecutableMessage) {\n\tmsg := iMsg.(*ExecutableMessageBanPost)\n\tBanPost(msg.FeedID, msg.PostID, msg.Reason)\n}\n\nfunc (h BanPostHandler) Type() string {\n\treturn ExecutableMessageBanPost{}.Type()\n}\n\nfunc (h *BanPostHandler) MessageFromJSON(ast *ujson.JSONASTNode) dao_interfaces.ExecutableMessage {\n\tmsg := &ExecutableMessageBanPost{}\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"feedId\", Value: &msg.FeedID},\n\t\t{Key: \"postId\", Value: &msg.PostID},\n\t\t{Key: \"reason\", Value: &msg.Reason},\n\t})\n\treturn msg\n}\n" + }, + { + "Name": "misc.gno", + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc getFeed(fid FeedID) *Feed {\n\tfidkey := feedIDKey(fid)\n\tfeed_, exists := gFeeds.Get(fidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tfeed := feed_.(*Feed)\n\treturn feed\n}\n\nfunc mustGetFeed(fid FeedID) *Feed {\n\tfeed := getFeed(fid)\n\tif feed == nil {\n\t\tpanic(\"Feed does not exist\")\n\t}\n\treturn feed\n}\n\nfunc incGetFeedID() FeedID {\n\tgFeedsCtr++\n\treturn FeedID(gFeedsCtr)\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t} else {\n\t\treturn user.Name\n\t}\n}\n\nfunc feedIDKey(fid FeedID) string {\n\treturn padZero(uint64(fid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) >= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) >= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc bytesToString(b []byte) string {\n\treturn base64.RawURLEncoding.EncodeToString(b)\n}\n\nfunc intToString(val int) string {\n\treturn strconv.Itoa(val)\n}\n" + }, + { + "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" + }, + { + "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" + }, + { + "Name": "render.gno", + "Body": "package social_feeds\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc renderFeed(parts []string) string {\n\t// /r/demo/social_feeds_v4:FEED_NAME\n\tname := parts[0]\n\tfeedI, exists := gFeedsByName.Get(name)\n\tif !exists {\n\t\treturn \"feed does not exist: \" + name\n\t}\n\treturn feedI.(*Feed).Render()\n}\n\nfunc renderPost(parts []string) string {\n\t// /r/demo/boards:FEED_NAME/POST_ID\n\tname := parts[0]\n\tfeedI, exists := gFeedsByName.Get(name)\n\tif !exists {\n\t\treturn \"feed does not exist: \" + name\n\t}\n\tpid, err := strconv.Atoi(parts[1])\n\tif err != nil {\n\t\treturn \"invalid thread id: \" + parts[1]\n\t}\n\tfeed := feedI.(*Feed)\n\tpost := feed.MustGetPost(PostID(pid))\n\treturn post.Render()\n}\n\nfunc renderFeedsList() string {\n\tstr := \"There are \" + intToString(gFeeds.Size()) + \" available feeds:\\n\\n\"\n\tgFeeds.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tfeed := value.(*Feed)\n\t\tstr += \" * [\" + feed.url + \" (FeedID: \" + feed.id.String() + \")](\" + feed.url + \")\\n\"\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderFeedsList()\n\t}\n\n\tparts := strings.Split(path, \"/\")\n\n\tif len(parts) == 1 {\n\t\t// /r/demo/social_feeds_v4:FEED_NAME\n\t\treturn renderFeed(parts)\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/social_feeds_v4:FEED_NAME/POST_ID\n\t\treturn renderPost(parts)\n\t}\n\n\treturn \"Not found\"\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "100000000", + "gas_fee": "10000000ugnot" + }, + "signatures": [ + { + "pub_key": { + "@type": "/tm.PubKeySecp256k1", + "value": "A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y" + }, + "signature": "fg01rLWLymXHVn9fE9vNyo4i2idOAEJn6KsPnlMT5JdrWqjzLScI65JVpJJErQUQMdpx/LvBPNVG3Atv/VGekg==" + } + ], + "memo": "" +} + +-- bye/bye.gno -- +package bye + +import ( + "encoding/base64" +) + +func Call(s string) { + base64.StdEncoding.DecodeString("hey") +} + diff --git a/gno.land/cmd/gnoland/testdata/maketx_call_pure.txtar b/gno.land/cmd/gnoland/testdata/maketx_call_pure.txtar new file mode 100644 index 00000000000..e3231eccc01 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/maketx_call_pure.txtar @@ -0,0 +1,32 @@ +# load the package +loadpkg gno.land/p/foo/call_package $WORK/package +loadpkg gno.land/r/foo/call_realm $WORK/realm + +# start a new node +gnoland start + +# 1. call to package ERROR +! gnokey maketx call -pkgpath gno.land/p/foo/call_package -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stderr '"gnokey" error: --= Error =--\nData: invalid package path' + +# 2. call to stdlibs ERROR +! gnokey maketx call -pkgpath strconv -func Itoa -args 11 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stderr '"gnokey" error: --= Error =--\nData: invalid package path' + +# 3. normal call to realm PASS +gnokey maketx call -pkgpath gno.land/r/foo/call_realm -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +-- package/package.gno -- +package call_package + +func Render() string { + return "notok" +} + +-- realm/realm.gno -- +package call_realm + +func Render() string { + return "ok" +} diff --git a/gno.land/cmd/gnoland/testdata/maketx_call_sponsor.txtar b/gno.land/cmd/gnoland/testdata/maketx_call_sponsor.txtar new file mode 100644 index 00000000000..e548af88e58 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/maketx_call_sponsor.txtar @@ -0,0 +1,26 @@ +# test for maketx call with sponsorship +loadpkg gno.land/r/foo/bar $WORK + +# start a new node +gnoland start + +## execute GetCaller +gnokey maketx call -pkgpath gno.land/r/foo/bar -func GetCaller -gas-fee 1000000ugnot -gas-wanted 2000000 --sponsor=${USER_ADDR_test1} -broadcast -chainid=tendermint_test test1 + +## compare GetCaller +stdout OK! +stdout 'GAS WANTED: 2000000' +stdout 'GAS USED: \d+' +stdout 'HEIGHT: \d+' +stdout 'EVENTS: \[\]' + +-- realm.gno -- +package call_realm + +import ( + "std" +) + +func GetCaller() string { + return std.PrevRealm().Addr().String() +} \ No newline at end of file diff --git a/gno.land/cmd/gnoland/testdata/prevrealm.txtar b/gno.land/cmd/gnoland/testdata/prevrealm.txtar new file mode 100644 index 00000000000..72a207fae22 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/prevrealm.txtar @@ -0,0 +1,184 @@ +# This tests ensure the consistency of the std.PrevRealm function, in the +# following situations: +# +# +# | Num | Msg Type | Call from | Entry Point | Result | +# |-----|:--------:|:-------------------:|:---------------:|:------------:| +# | 1 | MsgCall | wallet direct | myrlm.A() | user address | +# | 2 | | | myrlm.B() | user address | +# | 3 | | through /r/foo | myrlm.A() | r/foo | +# | 4 | | | myrlm.B() | r/foo | +# | 5 | | through /p/demo/bar | myrlm.A() | user address | +# | 6 | | | myrlm.B() | user address | +# | 7 | MsgRun | wallet direct | myrlm.A() | user address | +# | 8 | | | myrlm.B() | user address | +# | 9 | | through /r/foo | myrlm.A() | r/foo | +# | 10 | | | myrlm.B() | r/foo | +# | 11 | | through /p/demo/bar | myrlm.A() | user address | +# | 12 | | | myrlm.B() | user address | +# | 13 | MsgCall | wallet direct | std.PrevRealm() | user address | +# | 14 | MsgRun | wallet direct | std.PrevRealm() | user address | + +# Init +## deploy myrlm +loadpkg gno.land/r/myrlm $WORK/r/myrlm +## deploy r/foo +loadpkg gno.land/r/foo $WORK/r/foo +## deploy p/demo/bar +loadpkg gno.land/p/demo/bar $WORK/p/demo/bar + +## start a new node +gnoland start + +env RFOO_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 + +# Test cases +## 1. MsgCall -> myrlm.A: user address +gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +stdout ${USER_ADDR_test1} + +## 2. MsgCall -> myrealm.B -> myrlm.A: user address +gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +stdout ${USER_ADDR_test1} + +## 3. MsgCall -> r/foo.A -> myrlm.A: r/foo +gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +stdout ${RFOO_ADDR} + +## 4. MsgCall -> r/foo.B -> myrlm.B -> r/foo.A: r/foo +gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +stdout ${RFOO_ADDR} + +## remove due to update to maketx call can only call realm (case 5, 6, 13) +## 5. MsgCall -> p/demo/bar.A -> myrlm.A: user address +## gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +## stdout ${USER_ADDR_test1} + +## 6. MsgCall -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address +## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +## stdout ${USER_ADDR_test1} + +## 7. MsgRun -> myrlm.A: user address +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno +stdout ${USER_ADDR_test1} + +## 8. MsgRun -> myrealm.B -> myrlm.A: user address +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno +stdout ${USER_ADDR_test1} + +## 9. MsgRun -> r/foo.A -> myrlm.A: r/foo +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno +stdout ${RFOO_ADDR} + +## 10. MsgRun -> r/foo.B -> myrlm.B -> r/foo.A: r/foo +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno +stdout ${RFOO_ADDR} + +## 11. MsgRun -> p/demo/bar.A -> myrlm.A: user address +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno +stdout ${USER_ADDR_test1} + +## 12. MsgRun -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno +stdout ${USER_ADDR_test1} + +## 13. MsgCall -> std.PrevRealm(): user address +## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 +## stdout ${USER_ADDR_test1} + +## 14. MsgRun -> std.PrevRealm(): user address +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno +stdout ${USER_ADDR_test1} + +-- r/myrlm/myrlm.gno -- +package myrlm + +import "std" + +func A() string { + return std.PrevRealm().Addr().String() +} + +func B() string { + return A() +} +-- r/foo/foo.gno -- +package foo + +import "gno.land/r/myrlm" + +func A() string { + return myrlm.A() +} + +func B() string { + return myrlm.B() +} +-- p/demo/bar/bar.gno -- +package bar + +import "gno.land/r/myrlm" + +func A() string { + return myrlm.A() +} + +func B() string { + return myrlm.B() +} +-- run/myrlmA.gno -- +package main + +import myrlm "gno.land/r/myrlm" + +func main() { + println(myrlm.A()) +} +-- run/myrlmB.gno -- +package main + +import "gno.land/r/myrlm" + +func main() { + println(myrlm.B()) +} +-- run/fooA.gno -- +package main + +import "gno.land/r/foo" + +func main() { + println(foo.A()) +} +-- run/fooB.gno -- +package main + +import "gno.land/r/foo" + +func main() { + println(foo.B()) +} +-- run/barA.gno -- +package main + +import "gno.land/p/demo/bar" + +func main() { + println(bar.A()) +} +-- run/barB.gno -- +package main + +import "gno.land/p/demo/bar" + +func main() { + println(bar.B()) +} +-- run/baz.gno -- +package main + +import "std" + +func main() { + println(std.PrevRealm().Addr().String()) +} diff --git a/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar index abc3615d79c..71ef6400471 100644 --- a/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar @@ -7,10 +7,10 @@ adduser test2 gnoland start ## add realm_banker -gnokey maketx addpkg -pkgdir $WORK/short -pkgpath gno.land/r/test/realm_banker -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK/short -pkgpath gno.land/r/test/realm_banker -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## add realm_banker with long package_name -gnokey maketx addpkg -pkgdir $WORK/long -pkgpath gno.land/r/test/package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK/long -pkgpath gno.land/r/test/package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890 -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## test2 spend all balance gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 diff --git a/gno.land/cmd/gnoland/testdata/run.txtar b/gno.land/cmd/gnoland/testdata/run.txtar index 7246a10a1a4..f68346f09d6 100644 --- a/gno.land/cmd/gnoland/testdata/run.txtar +++ b/gno.land/cmd/gnoland/testdata/run.txtar @@ -11,6 +11,7 @@ stdout 'main: --- hello from foo ---' stdout 'OK!' stdout 'GAS WANTED: 200000' stdout 'GAS USED: ' +stdout 'TX HASH: ' -- bar/bar.gno -- package bar diff --git a/gno.land/cmd/gnoland/testdata/time-simple.txtar b/gno.land/cmd/gnoland/testdata/time_simple.txtar similarity index 100% rename from gno.land/cmd/gnoland/testdata/time-simple.txtar rename to gno.land/cmd/gnoland/testdata/time_simple.txtar diff --git a/gno.land/genesis/cleanup.sh b/gno.land/genesis/cleanup.sh index 84857c902dc..2f448f9cd5b 100755 --- a/gno.land/genesis/cleanup.sh +++ b/gno.land/genesis/cleanup.sh @@ -8,11 +8,11 @@ TARGET=genesis_txs.jsonl # first, bank transfers, without faucet cat $SRC | grep /bank.MsgSend | grep -v g1f4v282mwyhu29afke4vq5r2xzcm6z3ftnugcnv >> $TARGET -# then, r/users's invites -cat $SRC | grep '"pkg_path":"gno.land/r/users","func":"Invite"' >> $TARGET +# then, r/demo/users's invites +cat $SRC | grep '"pkg_path":"gno.land/r/demo/users","func":"Invite"' >> $TARGET -# then, r/users's registers -cat $SRC | grep '"pkg_path":"gno.land/r/users","func":"Register"' >> $TARGET +# then, r/demo/users's registers +cat $SRC | grep '"pkg_path":"gno.land/r/demo/users","func":"Register"' >> $TARGET ADMIN_ADDR=g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj JAE_ADDR=g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 @@ -21,7 +21,7 @@ JAE_ADDR=g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 cat $SRC | grep '"func":"CreateBoard","args":\["gnolang"\]' | sed "s/$JAE_ADDR/$ADMIN_ADDR/" >> $TARGET # r/boards:gnolang/xxx by jae, admin -cat $SRC | grep "caller\":\"$JAE_ADDR" | grep 'gno.land/r/boards","func":"CreateThread","args":\["1"' | sed "s/$JAE_ADDR/$ADMIN_ADDR/" >> $TARGET -cat $SRC | grep "caller\":\"$ADMIN_ADDR" | grep 'gno.land/r/boards","func":"CreateThread","args":\["1"' >> $TARGET +cat $SRC | grep "caller\":\"$JAE_ADDR" | grep 'gno.land/r/demo/boards","func":"CreateThread","args":\["1"' | sed "s/$JAE_ADDR/$ADMIN_ADDR/" >> $TARGET +cat $SRC | grep "caller\":\"$ADMIN_ADDR" | grep 'gno.land/r/demo/boards","func":"CreateThread","args":\["1"' >> $TARGET wc -l $SRC $TARGET diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 3baf9d3231b..daf9fbdc5d4 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -1,17 +1,17 @@ -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s: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\ng1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq:10\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1thlf3yct7n7ex70k0p62user0kn6mj6d3s0cg3\ng1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"njczE6xYdp01+CaUU/8/v0YC/NuZD06+qLind+ZZEEMNaRe/4Ln+4z7dG6HYlaWUMsyI1KCoB6NIehoE0PZ44Q=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz\ng187982000zsc493znqt828s90cmp6hcp2erhu6m\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6\ng1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t\n"]}],"fee":{"gas_wanted":"4000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"7AmlhZhsVkxCUl0bbpvpPMnIKihwtG7A5IFR6Tg4xStWLgaUr05XmWRKlO2xjstTtwbVKQT5mFL4h5wyX4SQzw=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"2000000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","administrator","g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"AqCqe0cS55Ym7/BvPDoCDyPP5q8284gecVQ2PMOlq/4lJpO9Q18SOWKI15dMEBY1pT0AYyhCeTirlsM1I3Y4Cg=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","send":"2000000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","zo_oma","Love is the encryption key\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A6yg5/iiktruezVw5vZJwLlGwyrvw8RlqOToTRMWXkE2"},"signature":"GGp+bVL2eEvKecPqgcULSABYOSnSMnJzfIsR8ZIRER1GGX/fOiCReX4WKMrGLVROJVfbLQkDRwvhS4TLHlSoSQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"2000000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","manfred","https://github.com/moul"]}],"fee":{"gas_wanted":"2000000","gas_fee":"2000000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"9CWeNbKx+hEL+RdHplAVAFntcrAVx5mK9tMqoywuHVoreH844n3yOxddQrGfBk6T2tMBmNWakERRqWZfS+bYAQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9","send":"2000000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","piupiu","@piux2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Ar68lqbU2YC63fbMcYUtJhYO3/66APM/EqF7m0nUjGyz"},"signature":"pTUpP0d/XlfVe3TH1hlaoLhKadzIKG1gtQ/Ueuat72p+659RWRea58Z0mk6GgPE/EeTbhMEY45zufevBdGJVoQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","anarcher","https://twitter.com/anarcher"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":"pf5xm8oWIQIOEwSGw4icPmynLXb1P1HxKfjeh8UStU1mlIBPKa7yppeIMPpAflC0o2zjFR7Axe7CimAebm3BHg=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"2000000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","ideamour","\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":"IQe64af878k6HjLDqIJeg27GXAVF6xS+96cDe2jMlxNV6+8sOcuUctp0GiWVnYfN4tpthC6d4WhBo+VlpHqkbg=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj:10\ng1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s:1\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8:1\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q:1\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj:1\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0:1\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz:1\ng187982000zsc493znqt828s90cmp6hcp2erhu6m:1\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl:1\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037:1\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5:1\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr:1\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz:1\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w:1\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz:1\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3:1\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0:1\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n:1\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac:1\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap:1\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv:1\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv:1\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq:1\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6:1\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q:1\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7:1\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k:1\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll:1\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd:1\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64:1\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw:1\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a:1\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc:1\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6:1\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6:1\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9:1\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea:1\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3:1\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp:1\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5:1\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf:1\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g:1\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r:1\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su:1\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69:1\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6:1\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1thlf3yct7n7ex70k0p62user0kn6mj6d3s0cg3\ng1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"njczE6xYdp01+CaUU/8/v0YC/NuZD06+qLind+ZZEEMNaRe/4Ln+4z7dG6HYlaWUMsyI1KCoB6NIehoE0PZ44Q=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz\ng187982000zsc493znqt828s90cmp6hcp2erhu6m\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6\ng1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t\n"]}],"fee":{"gas_wanted":"4000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"7AmlhZhsVkxCUl0bbpvpPMnIKihwtG7A5IFR6Tg4xStWLgaUr05XmWRKlO2xjstTtwbVKQT5mFL4h5wyX4SQzw=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","administrator","g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"AqCqe0cS55Ym7/BvPDoCDyPP5q8284gecVQ2PMOlq/4lJpO9Q18SOWKI15dMEBY1pT0AYyhCeTirlsM1I3Y4Cg=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","zo_oma","Love is the encryption key\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A6yg5/iiktruezVw5vZJwLlGwyrvw8RlqOToTRMWXkE2"},"signature":"GGp+bVL2eEvKecPqgcULSABYOSnSMnJzfIsR8ZIRER1GGX/fOiCReX4WKMrGLVROJVfbLQkDRwvhS4TLHlSoSQ=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","manfred","https://github.com/moul"]}],"fee":{"gas_wanted":"2000000","gas_fee":"200000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"9CWeNbKx+hEL+RdHplAVAFntcrAVx5mK9tMqoywuHVoreH844n3yOxddQrGfBk6T2tMBmNWakERRqWZfS+bYAQ=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","piupiu","@piux2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Ar68lqbU2YC63fbMcYUtJhYO3/66APM/EqF7m0nUjGyz"},"signature":"pTUpP0d/XlfVe3TH1hlaoLhKadzIKG1gtQ/Ueuat72p+659RWRea58Z0mk6GgPE/EeTbhMEY45zufevBdGJVoQ=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","anarcher","https://twitter.com/anarcher"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":"pf5xm8oWIQIOEwSGw4icPmynLXb1P1HxKfjeh8UStU1mlIBPKa7yppeIMPpAflC0o2zjFR7Axe7CimAebm3BHg=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","ideamour","\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":"IQe64af878k6HjLDqIJeg27GXAVF6xS+96cDe2jMlxNV6+8sOcuUctp0GiWVnYfN4tpthC6d4WhBo+VlpHqkbg=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards\nGetBoardIDFromName(\\\"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\ntestboard/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\ntestboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in Gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/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/banktest:](/r/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/banktest](/r/banktest) and the [quickstart guide](/r/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards\nGetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards\nBOARDNAME/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\ngnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} \ No newline at end of file +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} diff --git a/gno.land/pkg/gnoclient/README.md b/gno.land/pkg/gnoclient/README.md index 390559fb142..a2f00895dbd 100644 --- a/gno.land/pkg/gnoclient/README.md +++ b/gno.land/pkg/gnoclient/README.md @@ -1,7 +1,9 @@ # Gno.land Go Client -The Gno.land Go client is a dedicated library for interacting seamlessly with the Gno.land RPC API. -This library simplifies the process of querying or sending transactions to the Gno.land RPC API and interpreting the responses. +The gno.land Go client is a dedicated library for interacting seamlessly with the gno.land RPC API. +This library simplifies the process of querying or sending transactions to the gno.land RPC API and interpreting the responses. + +Documentation may be found at [gnoclient](../../../docs/reference/gnoclient). ## Installation @@ -11,12 +13,9 @@ Integrate this library into your Go project with the following command: ## Development Plan -The roadmap for the Gno.land Go client includes: +The roadmap for the gno.land Go client includes: -- **Initial Development:** Kickstart the development specifically for Gno.land. Subsequently, transition the generic functionalities to other modules like `tm2`, `gnovm`, `gnosdk`. +- **Initial Development:** Kickstart the development specifically for gno.land. Subsequently, transition the generic functionalities to other modules like `tm2`, `gnovm`, `gnosdk`. - **Integration:** Begin incorporating this library within various components such as `gno.land/cmd/*` and other external clients, including `gnoblog-client`, the Discord community faucet bot, and [GnoMobile](https://github.com/gnolang/gnomobile). - **Enhancements:** Once the generic client establishes a robust foundation, we aim to utilize code generation for contracts. This will streamline the creation of type-safe, contract-specific clients. -## Usage - -TODO: Documentation for usage is currently in development and will be available soon. diff --git a/gno.land/pkg/gnoclient/client.go b/gno.land/pkg/gnoclient/client.go index af57440d61e..426ce89d06d 100644 --- a/gno.land/pkg/gnoclient/client.go +++ b/gno.land/pkg/gnoclient/client.go @@ -2,6 +2,9 @@ package gnoclient import ( rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" ) // Client provides an interface for interacting with the blockchain. @@ -10,11 +13,48 @@ type Client struct { RPCClient rpcclient.Client // RPC client for blockchain communication } +// Public Client's interface +type IClient interface { + Query(cfg QueryCfg) (*ctypes.ResultABCIQuery, error) + QueryAccount(addr crypto.Address) (*std.BaseAccount, *ctypes.ResultABCIQuery, error) + QueryAppVersion() (string, *ctypes.ResultABCIQuery, error) + Render(pkgPath string, args string) (string, *ctypes.ResultABCIQuery, error) + QEval(pkgPath string, expression string) (string, *ctypes.ResultABCIQuery, error) + Block(height int64) (*ctypes.ResultBlock, error) + BlockResult(height int64) (*ctypes.ResultBlockResults, error) + LatestBlockHeight() (int64, error) + + Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTxCommit, error) + Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCommit, error) + Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) + AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) + + NewSponsorTransaction(cfg SponsorTxCfg, msgs ...Msg) (*std.Tx, error) + SignTransaction(tx std.Tx, accountNumber, sequenceNumber uint64) (*std.Tx, error) + ExecuteSponsorTransaction(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) +} + +var _ IClient = (*Client)(nil) + +// validateSigner checks that the Client's fields are correctly configured. +func (c *Client) validateClient() error { + if err := c.validateSigner(); err != nil { + return err + } + + if err := c.validateRPCClient(); err != nil { + return err + } + + return nil +} + // validateSigner checks that the signer is correctly configured. func (c *Client) validateSigner() error { if c.Signer == nil { return ErrMissingSigner } + return nil } @@ -23,5 +63,6 @@ func (c *Client) validateRPCClient() error { if c.RPCClient == nil { return ErrMissingRPCClient } + return nil } diff --git a/gno.land/pkg/gnoclient/client_queries.go b/gno.land/pkg/gnoclient/client_queries.go index a6c8ea60475..9d9d7305116 100644 --- a/gno.land/pkg/gnoclient/client_queries.go +++ b/gno.land/pkg/gnoclient/client_queries.go @@ -90,7 +90,7 @@ func (c *Client) Render(pkgPath string, args string) (string, *ctypes.ResultABCI } path := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", pkgPath, args)) + data := []byte(fmt.Sprintf("%s:%s", pkgPath, args)) qres, err := c.RPCClient.ABCIQuery(path, data) if err != nil { @@ -113,7 +113,7 @@ func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.Resul } path := "vm/qeval" - data := []byte(fmt.Sprintf("%s\n%s", pkgPath, expression)) + data := []byte(fmt.Sprintf("%s.%s", pkgPath, expression)) qres, err := c.RPCClient.ABCIQuery(path, data) if err != nil { diff --git a/gno.land/pkg/gnoclient/client_queries_test.go b/gno.land/pkg/gnoclient/client_queries_test.go new file mode 100644 index 00000000000..5119500842f --- /dev/null +++ b/gno.land/pkg/gnoclient/client_queries_test.go @@ -0,0 +1,165 @@ +package gnoclient + +import ( + "testing" + + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBlockResults(t *testing.T) { + t.Parallel() + + height := int64(5) + client := &Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{ + blockResults: func(height *int64) (*ctypes.ResultBlockResults, error) { + return &ctypes.ResultBlockResults{ + Height: *height, + Results: nil, + }, nil + }, + }, + } + + blockResult, err := client.BlockResult(height) + require.NoError(t, err) + assert.Equal(t, height, blockResult.Height) +} + +func TestLatestBlockHeight(t *testing.T) { + t.Parallel() + + latestHeight := int64(5) + + client := &Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{ + status: func() (*ctypes.ResultStatus, error) { + return &ctypes.ResultStatus{ + SyncInfo: ctypes.SyncInfo{ + LatestBlockHeight: latestHeight, + }, + }, nil + }, + }, + } + + head, err := client.LatestBlockHeight() + require.NoError(t, err) + assert.Equal(t, latestHeight, head) +} + +func TestBlockErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + height int64 + expectedError error + }{ + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + height: 1, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid height", + client: Client{ + &mockSigner{}, + &mockRPCClient{}, + }, + height: 0, + expectedError: ErrInvalidBlockHeight, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.Block(tc.height) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestBlockResultErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + height int64 + expectedError error + }{ + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + height: 1, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid height", + client: Client{ + &mockSigner{}, + &mockRPCClient{}, + }, + height: 0, + expectedError: ErrInvalidBlockHeight, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.BlockResult(tc.height) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestLatestBlockHeightErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + expectedError error + }{ + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + expectedError: ErrMissingRPCClient, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.LatestBlockHeight() + assert.Equal(t, int64(0), res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go deleted file mode 100644 index 04ebb8e27b8..00000000000 --- a/gno.land/pkg/gnoclient/client_test.go +++ /dev/null @@ -1,1268 +0,0 @@ -package gnoclient - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - ctypes "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" - "github.com/gnolang/gno/tm2/pkg/std" -) - -func TestRender(t *testing.T) { - t.Parallel() - testRealmPath := "gno.land/r/demo/deep/very/deep" - expectedRender := []byte("it works!") - - client := Client{ - Signer: &mockSigner{ - sign: func(cfg SignCfg) (*std.Tx, error) { - return &std.Tx{}, nil - }, - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{ - abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { - res := &ctypes.ResultABCIQuery{ - Response: abci.ResponseQuery{ - ResponseBase: abci.ResponseBase{ - Data: expectedRender, - }, - }, - } - return res, nil - }, - }, - } - - res, data, err := client.Render(testRealmPath, "") - assert.NoError(t, err) - assert.NotEmpty(t, data.Response.Data) - assert.NotEmpty(t, res) - assert.Equal(t, data.Response.Data, expectedRender) -} - -// Call tests -func TestCallSingle(t *testing.T) { - t.Parallel() - - client := Client{ - Signer: &mockSigner{ - sign: func(cfg SignCfg) (*std.Tx, error) { - return &std.Tx{}, nil - }, - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{ - broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res := &ctypes.ResultBroadcastTxCommit{ - DeliverTx: abci.ResponseDeliverTx{ - ResponseBase: abci.ResponseBase{ - Data: []byte("it works!"), - }, - }, - } - return res, nil - }, - }, - } - - cfg := BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - } - - msg := []MsgCall{ - { - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{""}, - Send: "100ugnot", - }, - } - - res, err := client.Call(cfg, msg...) - assert.NoError(t, err) - require.NotNil(t, res) - assert.Equal(t, string(res.DeliverTx.Data), "it works!") -} - -func TestCallMultiple(t *testing.T) { - t.Parallel() - - client := Client{ - Signer: &mockSigner{ - sign: func(cfg SignCfg) (*std.Tx, error) { - return &std.Tx{}, nil - }, - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{ - broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res := &ctypes.ResultBroadcastTxCommit{ - CheckTx: abci.ResponseCheckTx{ - ResponseBase: abci.ResponseBase{ - Error: nil, - Data: nil, - Events: nil, - Log: "", - Info: "", - }, - }, - } - - return res, nil - }, - }, - } - - cfg := BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - } - - msg := []MsgCall{ - { - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{""}, - Send: "100ugnot", - }, - { - PkgPath: "gno.land/r/demo/wugnot", - FuncName: "Deposit", - Args: []string{""}, - Send: "1000ugnot", - }, - { - PkgPath: "gno.land/r/demo/tamagotchi", - FuncName: "Feed", - Args: []string{""}, - Send: "", - }, - } - - res, err := client.Call(cfg, msg...) - assert.NoError(t, err) - assert.NotNil(t, res) -} - -func TestCallErrors(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - client Client - cfg BaseTxCfg - msgs []MsgCall - expectedError error - }{ - { - name: "Invalid Signer", - client: Client{ - Signer: nil, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, - }, - }, - expectedError: ErrMissingSigner, - }, - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, - }, - }, - expectedError: ErrMissingRPCClient, - }, - { - name: "Invalid Gas Fee", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "random/path", - FuncName: "RandomName", - }, - }, - expectedError: ErrInvalidGasFee, - }, - { - name: "Negative Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: -1, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "0 Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 0, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "Invalid PkgPath", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "", - FuncName: "RandomName", - Send: "", - Args: []string{}, - }, - }, - expectedError: ErrEmptyPkgPath, - }, - { - name: "Invalid FuncName", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgCall{ - { - PkgPath: "random/path", - FuncName: "", - Send: "", - Args: []string{}, - }, - }, - expectedError: ErrEmptyFuncName, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.Call(tc.cfg, tc.msgs...) - assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} - -func TestClient_Send_Errors(t *testing.T) { - t.Parallel() - - toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") - testCases := []struct { - name string - client Client - cfg BaseTxCfg - msgs []MsgSend - expectedError error - }{ - { - name: "Invalid Signer", - client: Client{ - Signer: nil, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: toAddress, - Send: "1ugnot", - }, - }, - expectedError: ErrMissingSigner, - }, - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: toAddress, - Send: "1ugnot", - }, - }, - expectedError: ErrMissingRPCClient, - }, - { - name: "Invalid Gas Fee", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: toAddress, - Send: "1ugnot", - }, - }, - expectedError: ErrInvalidGasFee, - }, - { - name: "Negative Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: -1, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: toAddress, - Send: "1ugnot", - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "0 Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 0, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: toAddress, - Send: "1ugnot", - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "Invalid To Address", - client: Client{ - Signer: &mockSigner{ - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: crypto.Address{}, - Send: "1ugnot", - }, - }, - expectedError: ErrInvalidToAddress, - }, - { - name: "Invalid Send Coins", - client: Client{ - Signer: &mockSigner{ - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgSend{ - { - ToAddress: toAddress, - Send: "-1ugnot", - }, - }, - expectedError: ErrInvalidSendAmount, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.Send(tc.cfg, tc.msgs...) - assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} - -// Run tests -func TestRunSingle(t *testing.T) { - t.Parallel() - - client := Client{ - Signer: &mockSigner{ - sign: func(cfg SignCfg) (*std.Tx, error) { - return &std.Tx{}, nil - }, - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{ - broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res := &ctypes.ResultBroadcastTxCommit{ - DeliverTx: abci.ResponseDeliverTx{ - ResponseBase: abci.ResponseBase{ - Data: []byte("hi gnoclient!\n"), - }, - }, - } - return res, nil - }, - }, - } - - cfg := BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - } - - fileBody := `package main -import ( - "std" - "gno.land/p/demo/ufmt" - "gno.land/r/demo/deep/very/deep" -) -func main() { - println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) -}` - - msg := MsgRun{ - Package: &std.MemPackage{ - Files: []*std.MemFile{ - { - Name: "main.gno", - Body: fileBody, - }, - }, - }, - Send: "", - } - - res, err := client.Run(cfg, msg) - assert.NoError(t, err) - require.NotNil(t, res) - assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) -} - -func TestRunMultiple(t *testing.T) { - t.Parallel() - - client := Client{ - Signer: &mockSigner{ - sign: func(cfg SignCfg) (*std.Tx, error) { - return &std.Tx{}, nil - }, - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{ - broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res := &ctypes.ResultBroadcastTxCommit{ - DeliverTx: abci.ResponseDeliverTx{ - ResponseBase: abci.ResponseBase{ - Data: []byte("hi gnoclient!\nhi gnoclient!\n"), - }, - }, - } - return res, nil - }, - }, - } - - cfg := BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - } - - fileBody := `package main -import ( - "std" - "gno.land/p/demo/ufmt" - "gno.land/r/demo/deep/very/deep" -) -func main() { - println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) -}` - - msg1 := MsgRun{ - Package: &std.MemPackage{ - Files: []*std.MemFile{ - { - Name: "main1.gno", - Body: fileBody, - }, - }, - }, - Send: "", - } - - msg2 := MsgRun{ - Package: &std.MemPackage{ - Files: []*std.MemFile{ - { - Name: "main2.gno", - Body: fileBody, - }, - }, - }, - Send: "", - } - - res, err := client.Run(cfg, msg1, msg2) - assert.NoError(t, err) - require.NotNil(t, res) - assert.Equal(t, "hi gnoclient!\nhi gnoclient!\n", string(res.DeliverTx.Data)) -} - -func TestRunErrors(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - client Client - cfg BaseTxCfg - msgs []MsgRun - expectedError error - }{ - { - name: "Invalid Signer", - client: Client{ - Signer: nil, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgRun{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Send: "", - }, - }, - expectedError: ErrMissingSigner, - }, - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgRun{}, - expectedError: ErrMissingRPCClient, - }, - { - name: "Invalid Gas Fee", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgRun{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Send: "", - }, - }, - expectedError: ErrInvalidGasFee, - }, - { - name: "Negative Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: -1, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgRun{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Send: "", - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "0 Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 0, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgRun{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Send: "", - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "Invalid Empty Package", - client: Client{ - Signer: &mockSigner{ - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgRun{ - { - Package: nil, - Send: "", - }, - }, - expectedError: ErrEmptyPackage, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.Run(tc.cfg, tc.msgs...) - assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} - -// AddPackage tests -func TestAddPackageErrors(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - client Client - cfg BaseTxCfg - msgs []MsgAddPackage - expectedError error - }{ - { - name: "Invalid Signer", - client: Client{ - Signer: nil, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgAddPackage{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Deposit: "", - }, - }, - expectedError: ErrMissingSigner, - }, - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgAddPackage{}, - expectedError: ErrMissingRPCClient, - }, - { - name: "Invalid Gas Fee", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgAddPackage{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Deposit: "", - }, - }, - expectedError: ErrInvalidGasFee, - }, - { - name: "Negative Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: -1, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgAddPackage{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Deposit: "", - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "0 Gas Wanted", - client: Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 0, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgAddPackage{ - { - Package: &std.MemPackage{ - Name: "", - Path: "", - Files: []*std.MemFile{ - { - Name: "file1.gno", - Body: "", - }, - }, - }, - Deposit: "", - }, - }, - expectedError: ErrInvalidGasWanted, - }, - { - name: "Invalid Empty Package", - client: Client{ - Signer: &mockSigner{ - info: func() keys.Info { - return &mockKeysInfo{ - getAddress: func() crypto.Address { - adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - return adr - }, - } - }, - }, - RPCClient: &mockRPCClient{}, - }, - cfg: BaseTxCfg{ - GasWanted: 100000, - GasFee: "10000ugnot", - AccountNumber: 1, - SequenceNumber: 1, - Memo: "Test memo", - }, - msgs: []MsgAddPackage{ - { - Package: nil, - Deposit: "", - }, - }, - expectedError: ErrEmptyPackage, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.AddPackage(tc.cfg, tc.msgs...) - assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} - -// Block tests -func TestBlock(t *testing.T) { - t.Parallel() - - height := int64(5) - client := &Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{ - block: func(height *int64) (*ctypes.ResultBlock, error) { - return &ctypes.ResultBlock{ - BlockMeta: &types.BlockMeta{ - BlockID: types.BlockID{}, - Header: types.Header{}, - }, - Block: &types.Block{ - Header: types.Header{ - Height: *height, - }, - Data: types.Data{}, - LastCommit: nil, - }, - }, nil - }, - }, - } - - block, err := client.Block(height) - require.NoError(t, err) - assert.Equal(t, height, block.Block.GetHeight()) -} - -func TestBlockResults(t *testing.T) { - t.Parallel() - - height := int64(5) - client := &Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{ - blockResults: func(height *int64) (*ctypes.ResultBlockResults, error) { - return &ctypes.ResultBlockResults{ - Height: *height, - Results: nil, - }, nil - }, - }, - } - - blockResult, err := client.BlockResult(height) - require.NoError(t, err) - assert.Equal(t, height, blockResult.Height) -} - -func TestLatestBlockHeight(t *testing.T) { - t.Parallel() - - latestHeight := int64(5) - - client := &Client{ - Signer: &mockSigner{}, - RPCClient: &mockRPCClient{ - status: func() (*ctypes.ResultStatus, error) { - return &ctypes.ResultStatus{ - SyncInfo: ctypes.SyncInfo{ - LatestBlockHeight: latestHeight, - }, - }, nil - }, - }, - } - - head, err := client.LatestBlockHeight() - require.NoError(t, err) - assert.Equal(t, latestHeight, head) -} - -func TestBlockErrors(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - client Client - height int64 - expectedError error - }{ - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - height: 1, - expectedError: ErrMissingRPCClient, - }, - { - name: "Invalid height", - client: Client{ - &mockSigner{}, - &mockRPCClient{}, - }, - height: 0, - expectedError: ErrInvalidBlockHeight, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.Block(tc.height) - assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} - -func TestBlockResultErrors(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - client Client - height int64 - expectedError error - }{ - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - height: 1, - expectedError: ErrMissingRPCClient, - }, - { - name: "Invalid height", - client: Client{ - &mockSigner{}, - &mockRPCClient{}, - }, - height: 0, - expectedError: ErrInvalidBlockHeight, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.BlockResult(tc.height) - assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} - -func TestLatestBlockHeightErrors(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - client Client - expectedError error - }{ - { - name: "Invalid RPCClient", - client: Client{ - &mockSigner{}, - nil, - }, - expectedError: ErrMissingRPCClient, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - res, err := tc.client.LatestBlockHeight() - assert.Equal(t, int64(0), res) - assert.ErrorIs(t, err, tc.expectedError) - }) - } -} diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index e306d737ede..460abda0bf9 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -4,66 +4,15 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" ) -var ( - ErrEmptyPackage = errors.New("empty package to run") - ErrEmptyPkgPath = errors.New("empty pkg path") - ErrEmptyFuncName = errors.New("empty function name") - ErrInvalidGasWanted = errors.New("invalid gas wanted") - ErrInvalidGasFee = errors.New("invalid gas fee") - ErrMissingSigner = errors.New("missing Signer") - ErrMissingRPCClient = errors.New("missing RPCClient") - ErrInvalidToAddress = errors.New("invalid send to address") - ErrInvalidSendAmount = errors.New("invalid send amount") -) - -// BaseTxCfg defines the base transaction configuration, shared by all message types -type BaseTxCfg struct { - GasFee string // Gas fee - GasWanted int64 // Gas wanted - AccountNumber uint64 // Account number - SequenceNumber uint64 // Sequence number - Memo string // Memo -} - -// MsgCall - syntax sugar for vm.MsgCall -type MsgCall struct { - PkgPath string // Package path - FuncName string // Function name - Args []string // Function arguments - Send string // Send amount -} - -// MsgSend - syntax sugar for bank.MsgSend -type MsgSend struct { - ToAddress crypto.Address // Send to address - Send string // Send amount -} - -// MsgRun - syntax sugar for vm.MsgRun -type MsgRun struct { - Package *std.MemPackage // Package to run - Send string // Send amount -} - -// MsgAddPackage - syntax sugar for vm.MsgAddPackage -type MsgAddPackage struct { - Package *std.MemPackage // Package to add - Deposit string // Coin deposit -} - // Call executes one or more MsgCall calls on the blockchain func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { - return nil, err - } - if err := c.validateRPCClient(); err != nil { + if err := c.validateClient(); err != nil { return nil, err } @@ -76,19 +25,24 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { // Validate MsgCall fields - if err := msg.validateMsgCall(); err != nil { + if err := msg.validateMsg(); err != nil { return nil, err } // Parse send coins - send, err := std.ParseCoins(msg.Send) + send, err := msg.getCoins() + if err != nil { + return nil, err + } + + caller, err := c.Signer.Info() if err != nil { return nil, err } // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgCall{ - Caller: c.Signer.Info().GetAddress(), + Caller: caller.GetAddress(), PkgPath: msg.PkgPath, Func: msg.FuncName, Args: msg.Args, @@ -116,10 +70,7 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx // Run executes one or more MsgRun calls on the blockchain func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { - return nil, err - } - if err := c.validateRPCClient(); err != nil { + if err := c.validateClient(); err != nil { return nil, err } @@ -132,24 +83,27 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { // Validate MsgCall fields - if err := msg.validateMsgRun(); err != nil { + if err := msg.validateMsg(); err != nil { return nil, err } // Parse send coins - send, err := std.ParseCoins(msg.Send) + send, err := msg.getCoins() if err != nil { return nil, err } - caller := c.Signer.Info().GetAddress() + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } msg.Package.Name = "main" msg.Package.Path = "" // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgRun{ - Caller: caller, + Caller: caller.GetAddress(), Package: msg.Package, Send: send, })) @@ -175,10 +129,7 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo // Send executes one or more MsgSend calls on the blockchain func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { - return nil, err - } - if err := c.validateRPCClient(); err != nil { + if err := c.validateClient(); err != nil { return nil, err } @@ -191,7 +142,7 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { // Validate MsgSend fields - if err := msg.validateMsgSend(); err != nil { + if err := msg.validateMsg(); err != nil { return nil, err } @@ -201,9 +152,14 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx return nil, err } + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + // Unwrap syntax sugar to vm.MsgSend slice vmMsgs = append(vmMsgs, std.Msg(bank.MsgSend{ - FromAddress: c.Signer.Info().GetAddress(), + FromAddress: caller.GetAddress(), ToAddress: msg.ToAddress, Amount: send, })) @@ -229,10 +185,7 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx // AddPackage executes one or more AddPackage calls on the blockchain func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { - return nil, err - } - if err := c.validateRPCClient(); err != nil { + if err := c.validateClient(); err != nil { return nil, err } @@ -245,21 +198,24 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { // Validate MsgCall fields - if err := msg.validateMsgAddPackage(); err != nil { + if err := msg.validateMsg(); err != nil { return nil, err } // Parse deposit coins - deposit, err := std.ParseCoins(msg.Deposit) + deposit, err := msg.getCoins() if err != nil { return nil, err } - caller := c.Signer.Info().GetAddress() + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgAddPackage{ - Creator: caller, + Creator: caller.GetAddress(), Package: msg.Package, Deposit: deposit, })) @@ -282,29 +238,163 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) } -// signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result -func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { - caller := c.Signer.Info().GetAddress() +// CreateTx creates an signed transaction for various types of messages which used for sponsorship +func (c *Client) NewSponsorTransaction(cfg SponsorTxCfg, msgs ...Msg) (*std.Tx, error) { + // Validate required client fields. + if err := c.validateClient(); err != nil { + return nil, err + } + + // Validate base transaction config + if err := cfg.validateSponsorTxConfig(); err != nil { + return nil, err + } + + // Ensure at least one message is provided + if len(msgs) == 0 { + return nil, ErrNoMessages + } + + // Ensure at least one signer is ready + signer, err := c.Signer.Info() + if err != nil { + return nil, err + } + + // Determine the type of the first user-provided message + firstMsgType := msgs[0].getType() + + // Parse Msg slice + vmMsgs := make([]std.Msg, 0, len(msgs)+1) - if sequenceNumber == 0 || accountNumber == 0 { - account, _, err := c.QueryAccount(caller) + // First msg in list must be MsgNoop + vmMsgs = append(vmMsgs, vm.MsgNoop{ + Caller: cfg.SponsorAddress, + }) + + for _, msg := range msgs { + // Check if all messages are of the same type + if msg.getType() != firstMsgType { + return nil, ErrMixedMessageTypes + } + + // Validate msg's fields + if err := msg.validateMsg(); err != nil { + return nil, err + } + + // Parse send/deposit coins + coins, err := msg.getCoins() if err != nil { - return nil, errors.Wrap(err, "query account") + return nil, err } - accountNumber = account.AccountNumber - sequenceNumber = account.Sequence + + switch m := msg.(type) { + case MsgCall: + // Unwrap syntax sugar to vm.MsgCall slice + vmMsgs = append(vmMsgs, vm.MsgCall{ + Caller: signer.GetAddress(), + PkgPath: m.PkgPath, + Func: m.FuncName, + Args: m.Args, + Send: coins, + }) + + case MsgSend: + // Unwrap syntax sugar to vm.MsgSend slice + vmMsgs = append(vmMsgs, bank.MsgSend{ + FromAddress: signer.GetAddress(), + ToAddress: m.ToAddress, + Amount: coins, + }) + + case MsgRun: + m.Package.Name = "main" + m.Package.Path = "" + + // Unwrap syntax sugar to vm.MsgRun slice + vmMsgs = append(vmMsgs, vm.MsgRun{ + Caller: signer.GetAddress(), + Package: m.Package, + Send: coins, + }) + + case MsgAddPackage: + // Unwrap syntax sugar to vm.MsgAddPackage slice + vmMsgs = append(vmMsgs, vm.MsgAddPackage{ + Creator: signer.GetAddress(), + Package: m.Package, + Deposit: coins, + }) + + default: + return nil, ErrInvalidMsgType + } + } + + // Parse gas fee + gasFeeCoins, err := std.ParseCoin(cfg.GasFee) + if err != nil { + return nil, err } + // Pack transaction + tx := &std.Tx{ + Msgs: vmMsgs, + Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), + Signatures: nil, + Memo: cfg.Memo, + } + + return tx, nil +} + +// SignTx signs a transaction using the client's signer +func (c *Client) SignTransaction(tx std.Tx, accountNumber, sequenceNumber uint64) (*std.Tx, error) { + // Ensure sequence number and account number are provided signCfg := SignCfg{ - UnsignedTX: tx, + Tx: tx, SequenceNumber: sequenceNumber, AccountNumber: accountNumber, } + signedTx, err := c.Signer.Sign(signCfg) if err != nil { return nil, errors.Wrap(err, "sign") } + return signedTx, nil +} + +// ExecuteSponsorTransaction allows broadcasting a pre-signed transaction (represented by `sponsorTx`) +// using the signer's account to pay transaction fees. The `sponsoree` account who signed `the sponsorTx“ before benefits +// from this transaction without incurring any gas costs +func (c *Client) ExecuteSponsorTransaction(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { + // Validate required client fields + if err := c.validateClient(); err != nil { + return nil, err + } + + // Validate basic transaction + if err := tx.ValidateBasic(); err != nil { + return nil, err + } + + // Ensure tx is a sponsor transaction + if !tx.IsSponsorTx() { + return nil, ErrInvalidSponsorTx + } + + return c.signAndBroadcastTxCommit(tx, accountNumber, sequenceNumber) +} + +// signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result +func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { + signedTx, err := c.SignTransaction(tx, accountNumber, sequenceNumber) + if err != nil { + return nil, errors.Wrap(err, "sign") + } + bz, err := amino.Marshal(signedTx) if err != nil { return nil, errors.Wrap(err, "marshaling tx binary bytes") @@ -324,5 +414,3 @@ func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumb return bres, nil } - -// TODO: Add more functionality, examples, and unit tests. diff --git a/gno.land/pkg/gnoclient/client_txs_test.go b/gno.land/pkg/gnoclient/client_txs_test.go new file mode 100644 index 00000000000..49ba3b4519b --- /dev/null +++ b/gno.land/pkg/gnoclient/client_txs_test.go @@ -0,0 +1,2341 @@ +package gnoclient + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/amino" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + ctypes "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" + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ( + addr1 = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + addr2 = crypto.MustAddressFromString("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") +) + +func TestRender(t *testing.T) { + t.Parallel() + testRealmPath := "gno.land/r/demo/deep/very/deep" + expectedRender := []byte("it works!") + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + res := &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + ResponseBase: abci.ResponseBase{ + Data: expectedRender, + }, + }, + } + return res, nil + }, + }, + } + + res, data, err := client.Render(testRealmPath, "") + assert.NoError(t, err) + assert.NotEmpty(t, data.Response.Data) + assert.NotEmpty(t, res) + assert.Equal(t, data.Response.Data, expectedRender) +} + +// Call tests +func TestCallSingle(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + msg := []MsgCall{ + { + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "100ugnot", + }, + } + + res, err := client.Call(cfg, msg...) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestCallSingle_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + msg := MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "100ugnot", + } + + tx, err := client.NewSponsorTransaction(cfg, msg) + assert.NoError(t, err) + + presignedTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*presignedTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestCallMultiple(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + CheckTx: abci.ResponseCheckTx{ + ResponseBase: abci.ResponseBase{ + Error: nil, + Data: nil, + Events: nil, + Log: "", + Info: "", + }, + }, + } + + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + msg := []MsgCall{ + { + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "100ugnot", + }, + { + PkgPath: "gno.land/r/demo/wugnot", + FuncName: "Deposit", + Args: []string{""}, + Send: "1000ugnot", + }, + { + PkgPath: "gno.land/r/demo/tamagotchi", + FuncName: "Feed", + Args: []string{""}, + Send: "", + }, + } + + res, err := client.Call(cfg, msg...) + assert.NoError(t, err) + assert.NotNil(t, res) +} + +func TestCallMultiple_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + msg1 := MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "100ugnot", + } + + msg2 := MsgCall{ + PkgPath: "gno.land/r/demo/wugnot", + FuncName: "Deposit", + Args: []string{""}, + Send: "1000ugnot", + } + + msg3 := MsgCall{ + PkgPath: "gno.land/r/demo/tamagotchi", + FuncName: "Feed", + Args: []string{""}, + Send: "", + } + + tx, err := client.NewSponsorTransaction(cfg, msg1, msg2, msg3) + assert.NoError(t, err) + + presignedTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*presignedTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestCallErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + cfg BaseTxCfg + msgs []MsgCall + expectedError error + }{ + { + name: "Invalid Signer", + client: Client{ + Signer: nil, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "random/path", + FuncName: "RandomName", + Send: "", + Args: []string{}, + }, + }, + expectedError: ErrMissingSigner, + }, + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "random/path", + FuncName: "RandomName", + Send: "", + Args: []string{}, + }, + }, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid Gas Fee", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "random/path", + FuncName: "RandomName", + }, + }, + expectedError: ErrInvalidGasFee, + }, + { + name: "Negative Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: -1, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "random/path", + FuncName: "RandomName", + Send: "", + Args: []string{}, + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "0 Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 0, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "random/path", + FuncName: "RandomName", + Send: "", + Args: []string{}, + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "Invalid PkgPath", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "", + FuncName: "RandomName", + Send: "", + Args: []string{}, + }, + }, + expectedError: ErrEmptyPkgPath, + }, + { + name: "Invalid FuncName", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgCall{ + { + PkgPath: "random/path", + FuncName: "", + Send: "", + Args: []string{}, + }, + }, + expectedError: ErrEmptyFuncName, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.Call(tc.cfg, tc.msgs...) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +// Send tests +func TestSendSingle(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + receiver, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + + msg := []MsgSend{ + { + ToAddress: receiver, + Send: "100ugnot", + }, + } + + res, err := client.Send(cfg, msg...) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestSendSingle_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + receiver, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + + msg := MsgSend{ + ToAddress: receiver, + Send: "100ugnot", + } + + tx, err := client.NewSponsorTransaction(cfg, msg) + assert.NoError(t, err) + + presignedTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*presignedTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestSendMultiple(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + receiver, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + + msg1 := MsgSend{ + ToAddress: receiver, + Send: "100ugnot", + } + + msg2 := MsgSend{ + ToAddress: receiver, + Send: "200ugnot", + } + + res, err := client.Send(cfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestSendMultiple_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("it works!"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + receiver, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + + msg1 := MsgSend{ + ToAddress: receiver, + Send: "100ugnot", + } + + msg2 := MsgSend{ + ToAddress: receiver, + Send: "200ugnot", + } + + tx, err := client.NewSponsorTransaction(cfg, msg1, msg2) + assert.NoError(t, err) + + presignedTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*presignedTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") +} + +func TestSendErrors(t *testing.T) { + t.Parallel() + + toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + testCases := []struct { + name string + client Client + cfg BaseTxCfg + msgs []MsgSend + expectedError error + }{ + { + name: "Invalid Signer", + client: Client{ + Signer: nil, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: toAddress, + Send: "1ugnot", + }, + }, + expectedError: ErrMissingSigner, + }, + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: toAddress, + Send: "1ugnot", + }, + }, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid Gas Fee", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: toAddress, + Send: "1ugnot", + }, + }, + expectedError: ErrInvalidGasFee, + }, + { + name: "Negative Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: -1, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: toAddress, + Send: "1ugnot", + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "0 Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 0, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: toAddress, + Send: "1ugnot", + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "Invalid To Address", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: crypto.Address{}, + Send: "1ugnot", + }, + }, + expectedError: ErrInvalidToAddress, + }, + { + name: "Invalid Send Coins", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgSend{ + { + ToAddress: toAddress, + Send: "-1ugnot", + }, + }, + expectedError: ErrInvalidAmount, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.Send(tc.cfg, tc.msgs...) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +// Run tests +func TestRunSingle(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + fileBody := `package main +import ( + "std" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/deep/very/deep" +) +func main() { + println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) +}` + + msg := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + res, err := client.Run(cfg, msg) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestRunSingle_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + fileBody := `package main +import ( + "std" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/deep/very/deep" +) +func main() { + println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) +}` + + msg := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + tx, err := client.NewSponsorTransaction(cfg, msg) + assert.NoError(t, err) + + presignedTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*presignedTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestRunMultiple(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\nhi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + fileBody := `package main +import ( + "std" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/deep/very/deep" +) +func main() { + println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) +}` + + msg1 := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main1.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + msg2 := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main2.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + res, err := client.Run(cfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\nhi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestRunMultiple_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\nhi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + fileBody := `package main +import ( + "std" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/deep/very/deep" +) +func main() { + println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) +}` + + msg1 := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main1.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + msg2 := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main2.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + tx, err := client.NewSponsorTransaction(cfg, msg1, msg2) + assert.NoError(t, err) + + presignedTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*presignedTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\nhi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestRunErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + cfg BaseTxCfg + msgs []MsgRun + expectedError error + }{ + { + name: "Invalid Signer", + client: Client{ + Signer: nil, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgRun{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Send: "", + }, + }, + expectedError: ErrMissingSigner, + }, + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgRun{}, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid Gas Fee", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgRun{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Send: "", + }, + }, + expectedError: ErrInvalidGasFee, + }, + { + name: "Negative Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: -1, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgRun{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Send: "", + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "0 Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 0, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgRun{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Send: "", + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "Invalid Empty Package", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgRun{ + { + Package: nil, + Send: "", + }, + }, + expectedError: ErrEmptyPackage, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.Run(tc.cfg, tc.msgs...) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +// AddPackage tests +func TestAddPackageSingle(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + msg := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + } + + res, err := client.AddPackage(cfg, msg) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestAddPackageSingle_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + msg := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + } + + tx, err := client.NewSponsorTransaction(cfg, msg) + assert.NoError(t, err) + + sponsorTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*sponsorTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestAddPackageMultiple(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + return &std.Tx{}, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + } + + msgs := []MsgAddPackage{ + { + Package: &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + }, + { + Package: &std.MemPackage{ + Name: "goodbye", + Path: "gno.land/p/demo/goodbye", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + }, + } + + res, err := client.AddPackage(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestAddPackageMultiple_Sponsor(t *testing.T) { + t.Parallel() + + client := Client{ + Signer: &mockSigner{ + sign: func(cfg SignCfg) (*std.Tx, error) { + cfg.Tx.Signatures = make([]std.Signature, 2) + return &cfg.Tx, nil + }, + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{ + broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("hi gnoclient!\n"), + }, + }, + } + return res, nil + }, + }, + } + + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + } + + msg1 := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + } + + msg2 := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "goodbye", + Path: "gno.land/p/demo/goodbye", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + } + + tx, err := client.NewSponsorTransaction(cfg, msg1, msg2) + assert.NoError(t, err) + + sponsorTx, err := client.SignTransaction(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + res, err := client.ExecuteSponsorTransaction(*sponsorTx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) +} + +func TestAddPackageErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + cfg BaseTxCfg + msgs []MsgAddPackage + expectedError error + }{ + { + name: "Invalid Signer", + client: Client{ + Signer: nil, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgAddPackage{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + }, + }, + expectedError: ErrMissingSigner, + }, + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgAddPackage{}, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid Gas Fee", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgAddPackage{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + }, + }, + expectedError: ErrInvalidGasFee, + }, + { + name: "Negative Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: -1, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgAddPackage{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "0 Gas Wanted", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 0, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgAddPackage{ + { + Package: &std.MemPackage{ + Name: "", + Path: "", + Files: []*std.MemFile{ + { + Name: "file1.gno", + Body: "", + }, + }, + }, + Deposit: "", + }, + }, + expectedError: ErrInvalidGasWanted, + }, + { + name: "Invalid Empty Package", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + msgs: []MsgAddPackage{ + { + Package: nil, + Deposit: "", + }, + }, + expectedError: ErrEmptyPackage, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.AddPackage(tc.cfg, tc.msgs...) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestNewSponsorTransaction(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + cfg SponsorTxCfg + msgs []Msg + expectedError error + }{ + { + name: "Invalid Client", + client: Client{ + Signer: nil, // invalid signer + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + expectedError: ErrMissingSigner, + }, + { + name: "Invalid SponsorTxCfg", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: -1, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: crypto.Address{}, // invalid sponsor address + }, + expectedError: ErrInvalidSponsorAddress, + }, + { + name: "Empty message list", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + + msgs: []Msg{}, // no messages provided + + expectedError: ErrNoMessages, + }, + { + name: "Signer not found", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return nil, errors.New("failed to get signer info") // signer not found + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + + msgs: []Msg{}, // no messages provided + + expectedError: ErrNoMessages, + }, + { + name: "All messages aren't the same type", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + + // MixedMessage is invalid + msgs: []Msg{ + MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "100ugnot", + }, + MsgSend{ + ToAddress: addr1, + Send: "100ugnot", + }, + }, + expectedError: ErrMixedMessageTypes, + }, + { + name: "At least one invalid message", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + msgs: []Msg{ + // invalid message send + MsgSend{ + ToAddress: crypto.Address{}, + Send: "10000ugnot", + }, + }, + expectedError: ErrInvalidToAddress, + }, + { + name: "Failed to parse coin from message", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + msgs: []Msg{ + MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "xxx", // invalid coin + }, + }, + expectedError: ErrInvalidAmount, + }, + { + name: "Invalid message type", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + msgs: []Msg{ + mockMsg{}, // invalid msg type + }, + expectedError: ErrInvalidMsgType, + }, + { + name: "Failed to parse gas fee", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + }, + RPCClient: &mockRPCClient{}, + }, + cfg: SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "xxx", // invalid gas fee + AccountNumber: 1, + SequenceNumber: 1, + Memo: "Test memo", + }, + SponsorAddress: addr2, + }, + msgs: []Msg{ + MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{""}, + Send: "100ugnot", + }, + }, + expectedError: errors.New("invalid coin expression: xxx"), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.NewSponsorTransaction(tc.cfg, tc.msgs...) + assert.Nil(t, res) + assert.Equal(t, err.Error(), tc.expectedError.Error()) + }) + } +} + +func TestSignTransaction(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + tx std.Tx + expectedError error + }{ + { + name: "Failed to sign transaction", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + sign: func(cfg SignCfg) (*std.Tx, error) { + return nil, errors.New("failed to sign transaction") + }, + }, + RPCClient: &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + acc := std.NewBaseAccount(addr1, nil, nil, 0, 0) + accData, _ := amino.MarshalJSON(acc) + + return &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + ResponseBase: abci.ResponseBase{ + Data: accData, + }, + }, + }, nil + }, + }, + }, + tx: std.Tx{}, + expectedError: errors.New("failed to sign transaction"), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.SignTransaction(tc.tx, 0, 0) + assert.Nil(t, res) + assert.Equal(t, err.Error(), tc.expectedError.Error()) + }) + } +} + +func TestExecuteSponsorTransaction(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + tx std.Tx + expectedError error + }{ + { + name: "Invalid Client", + client: Client{ + Signer: nil, + RPCClient: &mockRPCClient{}, + }, + tx: std.Tx{}, + expectedError: ErrMissingSigner, + }, + { + name: "Invalid transaction", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + tx: std.Tx{ + Fee: std.NewFee(1000, std.NewCoin("ugnot", 10)), + Msgs: []std.Msg{ + vm.MsgCall{ + Caller: addr1, + }, + }, + Signatures: []std.Signature{}, // no signatures provided + }, + expectedError: errors.New("no signatures error"), + }, + { + name: "tx is not a sponsor transaction", + client: Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{}, + }, + tx: std.Tx{ + Fee: std.NewFee(1000, std.NewCoin("ugnot", 10)), + Msgs: []std.Msg{ // missing noop msg + bank.MsgSend{ + FromAddress: addr1, + ToAddress: addr2, + Amount: std.NewCoins(std.NewCoin("gnot", 1000)), + }, + }, + Signatures: []std.Signature{ + { + PubKey: nil, + Signature: nil, + }, + }, + }, + expectedError: ErrInvalidSponsorTx, + }, + { + name: "signAndBroadcastTxCommit error", + client: Client{ + Signer: &mockSigner{ + info: func() (keys.Info, error) { + return &mockKeysInfo{ + getAddress: func() crypto.Address { + return addr1 + }, + }, nil + }, + sign: func(cfg SignCfg) (*std.Tx, error) { + return nil, errors.New("failed to sign tx") // failed to sign tx + }, + }, + RPCClient: &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + acc := std.NewBaseAccount(addr1, std.NewCoins(), nil, 0, 0) + accData, _ := amino.MarshalJSON(acc) + + return &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + ResponseBase: abci.ResponseBase{ + Data: accData, + }, + }, + }, nil + }, + }, + }, + tx: std.Tx{ + Fee: std.NewFee(1000, std.NewCoin("ugnot", 10)), + Msgs: []std.Msg{ + vm.MsgNoop{ + Caller: addr2, + }, + bank.MsgSend{ + FromAddress: addr1, + ToAddress: addr2, + Amount: std.NewCoins(std.NewCoin("gnot", 1000)), + }, + }, + Signatures: []std.Signature{ + { + PubKey: nil, + Signature: nil, + }, + { + PubKey: nil, + Signature: nil, + }, + }, + }, + expectedError: errors.New("failed to sign tx"), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.ExecuteSponsorTransaction(tc.tx, 0, 0) + assert.Nil(t, res) + assert.Equal(t, err.Error(), tc.expectedError.Error()) + }) + } +} diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index bd8079517d8..7b25608b137 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -11,12 +11,14 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/bip39" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// Call tests func TestCallSingle_Integration(t *testing.T) { // Set up in-memory node config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) @@ -24,7 +26,9 @@ func TestCallSingle_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -53,12 +57,108 @@ func TestCallSingle_Integration(t *testing.T) { // Execute call res, err := client.Call(baseCfg, msg) + require.NoError(t, err) + + expected := "(\"hi test argument\" string)\n\n" + got := string(res.DeliverTx.Data) + + assert.Equal(t, expected, got) +} + +func TestCallSingle_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sponsoree := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + sponsoreeInfo, err := sponsoree.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + sponsoreeClient := Client{ + Signer: sponsoree, + RPCClient: rpcClient, + } + + // Fetch sponsoree account information before the transaction + var sponsoreeAccountNumber uint64 = 0 + var sponsoreeSequence uint64 = 0 + + sponsoreeBefore, _, _ := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + if sponsoreeBefore != nil { + sponsoreeAccountNumber = sponsoreeBefore.AccountNumber + sponsoreeSequence = sponsoreeBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + Memo: "Test memo", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + // Create the message for the transaction + msg := MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{"test argument"}, + } + + // Sponsoree creates a new sponsor transaction + tx, err := sponsoreeClient.NewSponsorTransaction(cfg, msg) + require.NoError(t, err) + + // Sponsoree signs the transaction + sponsorTx, err := sponsoreeClient.SignTransaction(*tx, sponsoreeAccountNumber, sponsoreeSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sponsoree + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + // Check the result of the transaction execution expected := "(\"hi test argument\" string)\n\n" got := string(res.DeliverTx.Data) assert.Nil(t, err) assert.Equal(t, expected, got) + + // Query sponsoree's balance after the transaction + sponsoreeAfter, _, err := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + require.NoError(t, err) + assert.Equal(t, std.Coins(nil), sponsoreeAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) } func TestCallMultiple_Integration(t *testing.T) { @@ -68,7 +168,8 @@ func TestCallMultiple_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -107,12 +208,115 @@ func TestCallMultiple_Integration(t *testing.T) { // Execute call res, err := client.Call(baseCfg, msg1, msg2) + require.NoError(t, err) + + got := string(res.DeliverTx.Data) + assert.Equal(t, expected, got) +} + +func TestCallMultiple_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sponsoree := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + sponsoreeInfo, err := sponsoree.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + sponsoreeClient := Client{ + Signer: sponsoree, + RPCClient: rpcClient, + } + + // Fetch sponsoree account information before the transaction + var sponsoreeAccountNumber uint64 = 0 + var sponsoreeSequence uint64 = 0 + + sponsoreeBefore, _, _ := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + if sponsoreeBefore != nil { + sponsoreeAccountNumber = sponsoreeBefore.AccountNumber + sponsoreeSequence = sponsoreeBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 100000, + GasFee: "10000ugnot", + Memo: "Test memo", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + // Create multiple messages for the transaction + msg1 := MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{"test1"}, + } + + msg2 := MsgCall{ + PkgPath: "gno.land/r/demo/deep/very/deep", + FuncName: "Render", + Args: []string{"test2"}, + } + + // Sponsoree creates a new sponsor transaction + tx, err := sponsoreeClient.NewSponsorTransaction(cfg, msg1, msg2) + require.NoError(t, err) + + // Sponsoree signs the transaction + sponsorTx, err := sponsoreeClient.SignTransaction(*tx, sponsoreeAccountNumber, sponsoreeSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sponsoree + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "(\"hi test1\" string)\n\n(\"hi test2\" string)\n\n" got := string(res.DeliverTx.Data) + assert.Nil(t, err) assert.Equal(t, expected, got) + + // Query sponsoree's balance after the transaction + sponsoreeAfter, _, err := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + require.NoError(t, err) + assert.Equal(t, std.Coins(nil), sponsoreeAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) } +// Send tests func TestSendSingle_Integration(t *testing.T) { // Set up in-memory node config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) @@ -120,7 +324,8 @@ func TestSendSingle_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -149,12 +354,12 @@ func TestSendSingle_Integration(t *testing.T) { // Execute send res, err := client.Send(baseCfg, msg) - assert.Nil(t, err) + require.NoError(t, err) assert.Equal(t, "", string(res.DeliverTx.Data)) // Get the new account balance account, _, err := client.QueryAccount(toAddress) - assert.Nil(t, err) + require.NoError(t, err) expected := std.Coins{{"ugnot", int64(amount)}} got := account.GetCoins() @@ -162,6 +367,121 @@ func TestSendSingle_Integration(t *testing.T) { assert.Equal(t, expected, got) } +func TestSendSingle_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sender := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + senderInfo, err := sender.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + senderClient := Client{ + Signer: sender, + RPCClient: rpcClient, + } + + // Ensure sender has enough money to make msg send + _, err = sponsorClient.Send(BaseTxCfg{ + GasWanted: 1000000, + GasFee: "100000ugnot", + Memo: "Test memo", + }, MsgSend{ + ToAddress: senderInfo.GetAddress(), + Send: "100000ugnot", + }) + require.NoError(t, err) + + // Fetch sender account information before the transaction + var senderAccountNumber uint64 = 0 + var senderSequence uint64 = 0 + + senderBefore, _, _ := senderClient.QueryAccount(senderInfo.GetAddress()) + if senderBefore != nil { + senderAccountNumber = senderBefore.AccountNumber + senderSequence = senderBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 1000000, + GasFee: "100000ugnot", + Memo: "Test memo", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + + // Create the message for the transaction + msg := MsgSend{ + ToAddress: toAddress, + Send: "10000ugnot", + } + + // sender creates a new sponsor transaction + tx, err := senderClient.NewSponsorTransaction(cfg, msg) + require.NoError(t, err) + + // sender signs the transaction + sponsorTx, err := senderClient.SignTransaction(*tx, senderAccountNumber, senderSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sender + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "" + got := string(res.DeliverTx.Data) + + assert.Nil(t, err) + assert.Equal(t, expected, got) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) + + // Query sender's balance after the transaction + senderAfter, _, err := senderClient.QueryAccount(senderInfo.GetAddress()) + require.NoError(t, err) + expectedSenderAfter := senderBefore.GetCoins().Sub(std.MustParseCoins(msg.Send)) + assert.Equal(t, expectedSenderAfter, senderAfter.GetCoins()) + + // Query to's balance after the transaction + toAfter, _, err := sponsorClient.QueryAccount(toAddress) + require.NoError(t, err) + expectedToAfter := std.NewCoins(std.MustParseCoin(msg.Send)) + assert.Equal(t, expectedToAfter, toAfter.GetCoins()) +} + func TestSendMultiple_Integration(t *testing.T) { // Set up in-memory node config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) @@ -169,7 +489,8 @@ func TestSendMultiple_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -205,12 +526,12 @@ func TestSendMultiple_Integration(t *testing.T) { // Execute send res, err := client.Send(baseCfg, msg1, msg2) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, "", string(res.DeliverTx.Data)) // Get the new account balance account, _, err := client.QueryAccount(toAddress) - assert.Nil(t, err) + assert.NoError(t, err) expected := std.Coins{{"ugnot", int64(amount1 + amount2)}} got := account.GetCoins() @@ -218,6 +539,128 @@ func TestSendMultiple_Integration(t *testing.T) { assert.Equal(t, expected, got) } +func TestSendMultiple_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sender := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + senderInfo, err := sender.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + senderClient := Client{ + Signer: sender, + RPCClient: rpcClient, + } + + // Ensure sender has enough money to make msg send + _, err = sponsorClient.Send(BaseTxCfg{ + GasWanted: 1000000, + GasFee: "100000ugnot", + Memo: "Test memo", + }, MsgSend{ + ToAddress: senderInfo.GetAddress(), + Send: "100000ugnot", + }) + require.NoError(t, err) + + // Fetch sender account information before the transaction + var senderAccountNumber uint64 = 0 + var senderSequence uint64 = 0 + + senderBefore, _, _ := senderClient.QueryAccount(senderInfo.GetAddress()) + if senderBefore != nil { + senderAccountNumber = senderBefore.AccountNumber + senderSequence = senderBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasWanted: 1000000, + GasFee: "100000ugnot", + Memo: "Test memo", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") + + // Create the messages for the transaction + var amount1 int64 = 20000 + msg1 := MsgSend{ + ToAddress: toAddress, + Send: std.NewCoin("ugnot", amount1).String(), + } + + var amount2 int64 = 20000 + msg2 := MsgSend{ + ToAddress: toAddress, + Send: std.NewCoin("ugnot", amount2).String(), + } + + // sender creates a new sponsor transaction + tx, err := senderClient.NewSponsorTransaction(cfg, msg1, msg2) + require.NoError(t, err) + + // sender signs the transaction + sponsorTx, err := senderClient.SignTransaction(*tx, senderAccountNumber, senderSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sender + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "" + got := string(res.DeliverTx.Data) + + assert.Nil(t, err) + assert.Equal(t, expected, got) + + // Query sender's balance after the transaction + senderAfter, _, err := senderClient.QueryAccount(senderInfo.GetAddress()) + require.NoError(t, err) + expectSenderAfter := senderBefore.GetCoins().Sub(std.NewCoins(std.NewCoin("ugnot", amount1+amount2))) + assert.Equal(t, expectSenderAfter, senderAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) + + // Query to's balance after the transaction + toAfter, _, err := sponsorClient.QueryAccount(toAddress) + require.NoError(t, err) + expectToAfter := std.NewCoins(std.NewCoin("ugnot", amount1+amount2)) + assert.Equal(t, expectToAfter, toAfter.GetCoins()) +} + // Run tests func TestRunSingle_Integration(t *testing.T) { // Set up in-memory node @@ -226,7 +669,8 @@ func TestRunSingle_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -276,44 +720,159 @@ func main() { assert.Equal(t, string(res.DeliverTx.Data), "- before: 0\n- after: 10\n") } -// Run tests -func TestRunMultiple_Integration(t *testing.T) { - // Set up in-memory node +func TestRunSingle_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) defer node.Stop() - // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sponsoree := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + sponsoreeInfo, err := sponsoree.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) - client := Client{ - Signer: signer, + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, RPCClient: rpcClient, } - // Make Tx config - baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", - GasWanted: 8000000, - AccountNumber: 0, - SequenceNumber: 0, - Memo: "", + sponsoreeClient := Client{ + Signer: sponsoree, + RPCClient: rpcClient, } - fileBody1 := `package main -import ( - "gno.land/p/demo/ufmt" - "gno.land/r/demo/tests" -) -func main() { - println(ufmt.Sprintf("- before: %d", tests.Counter())) - for i := 0; i < 10; i++ { - tests.IncCounter() + // Fetch sponsoree account information before the transaction + var sponsoreeAccountNumber uint64 = 0 + var sponsoreeSequence uint64 = 0 + + sponsoreeBefore, _, _ := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + if sponsoreeBefore != nil { + sponsoreeAccountNumber = sponsoreeBefore.AccountNumber + sponsoreeSequence = sponsoreeBefore.Sequence } - println(ufmt.Sprintf("- after: %d", tests.Counter())) -}` + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasFee: "10000ugnot", + GasWanted: 8000000, + Memo: "", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + fileBody := `package main + import ( + "gno.land/p/demo/ufmt" + "gno.land/r/demo/tests" + ) + func main() { + println(ufmt.Sprintf("- before: %d", tests.Counter())) + for i := 0; i < 10; i++ { + tests.IncCounter() + } + println(ufmt.Sprintf("- after: %d", tests.Counter())) + }` + + // Create the message for the transaction + msg := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: fileBody, + }, + }, + }, + Send: "", + } + + // Sponsoree creates a new sponsor transaction + tx, err := sponsoreeClient.NewSponsorTransaction(cfg, msg) + require.NoError(t, err) + + // Sponsoree signs the transaction + sponsorTx, err := sponsoreeClient.SignTransaction(*tx, sponsoreeAccountNumber, sponsoreeSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sponsoree + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "- before: 0\n- after: 10\n" + got := string(res.DeliverTx.Data) + + assert.Nil(t, err) + assert.Equal(t, expected, got) + + // Query sponsoree's balance after the transaction + sponsoreeAfter, _, err := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + require.NoError(t, err) + assert.Equal(t, std.Coins(nil), sponsoreeAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) +} + +func TestRunMultiple_Integration(t *testing.T) { + // Set up in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Init Signer & RPCClient + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + client := Client{ + Signer: signer, + RPCClient: rpcClient, + } + + // Make Tx config + baseCfg := BaseTxCfg{ + GasFee: "10000ugnot", + GasWanted: 8000000, + AccountNumber: 0, + SequenceNumber: 0, + Memo: "", + } + + fileBody1 := `package main +import ( + "gno.land/p/demo/ufmt" + "gno.land/r/demo/tests" +) +func main() { + println(ufmt.Sprintf("- before: %d", tests.Counter())) + for i := 0; i < 10; i++ { + tests.IncCounter() + } + println(ufmt.Sprintf("- after: %d", tests.Counter())) +}` fileBody2 := `package main import ( @@ -356,6 +915,142 @@ func main() { assert.Equal(t, expected, string(res.DeliverTx.Data)) } +func TestRunMultiple_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sponsoree := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + sponsoreeInfo, err := sponsoree.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + sponsoreeClient := Client{ + Signer: sponsoree, + RPCClient: rpcClient, + } + + // Fetch sponsoree account information before the transaction + var sponsoreeAccountNumber uint64 = 0 + var sponsoreeSequence uint64 = 0 + + sponsoreeBefore, _, _ := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + if sponsoreeBefore != nil { + sponsoreeAccountNumber = sponsoreeBefore.AccountNumber + sponsoreeSequence = sponsoreeBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasFee: "10000ugnot", + GasWanted: 8000000, + Memo: "", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + fileBody1 := `package main + import ( + "gno.land/p/demo/ufmt" + "gno.land/r/demo/tests" + ) + func main() { + println(ufmt.Sprintf("- before: %d", tests.Counter())) + for i := 0; i < 10; i++ { + tests.IncCounter() + } + println(ufmt.Sprintf("- after: %d", tests.Counter())) + }` + + fileBody2 := `package main + import ( + "gno.land/p/demo/ufmt" + "gno.land/r/demo/deep/very/deep" + ) + func main() { + println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) + }` + + // Make Msg configs + msg1 := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: fileBody1, + }, + }, + }, + Send: "", + } + msg2 := MsgRun{ + Package: &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: fileBody2, + }, + }, + }, + Send: "", + } + + // Sponsoree creates a new sponsor transaction + tx, err := sponsoreeClient.NewSponsorTransaction(cfg, msg1, msg2) + require.NoError(t, err) + + // Sponsoree signs the transaction + sponsorTx, err := sponsoreeClient.SignTransaction(*tx, sponsoreeAccountNumber, sponsoreeSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sponsoree + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "- before: 0\n- after: 10\nhi gnoclient!\n" + got := string(res.DeliverTx.Data) + + assert.Nil(t, err) + assert.Equal(t, expected, got) + + // Query sponsoree's balance after the transaction + sponsoreeAfter, _, err := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + require.NoError(t, err) + assert.Equal(t, std.Coins(nil), sponsoreeAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) +} + +// AddPackage tests func TestAddPackageSingle_Integration(t *testing.T) { // Set up in-memory node config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) @@ -363,7 +1058,8 @@ func TestAddPackageSingle_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -409,7 +1105,7 @@ func Echo(str string) string { // Execute AddPackage _, err = client.AddPackage(baseCfg, msg) - assert.Nil(t, err) + assert.NoError(t, err) // Check for deployed file on the node query, err := client.Query(QueryCfg{ @@ -425,6 +1121,145 @@ func Echo(str string) string { assert.Equal(t, baseAcc.GetCoins().String(), deposit) } +func TestAddPackageSingle_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sponsoree := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + sponsoreeInfo, err := sponsoree.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + sponsoreeClient := Client{ + Signer: sponsoree, + RPCClient: rpcClient, + } + + // Ensure sponsoree has enough money to make msg addpackage + _, err = sponsorClient.Send(BaseTxCfg{ + GasWanted: 1000000, + GasFee: "100000ugnot", + Memo: "Test memo", + }, MsgSend{ + ToAddress: sponsoreeInfo.GetAddress(), + Send: "100000ugnot", + }) + require.NoError(t, err) + + // Fetch sponsoree account information before the transaction + var sponsoreeAccountNumber uint64 = 0 + var sponsoreeSequence uint64 = 0 + + sponsoreeBefore, _, _ := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + if sponsoreeBefore != nil { + sponsoreeAccountNumber = sponsoreeBefore.AccountNumber + sponsoreeSequence = sponsoreeBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasFee: "10000ugnot", + GasWanted: 8000000, + Memo: "", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + body := `package echo + +func Echo(str string) string { + return str +}` + + fileName := "echo.gno" + deploymentPath := "gno.land/p/demo/integration/test/echo" + deposit := "100ugnot" + + // Make Msg config + msg := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "echo", + Path: deploymentPath, + Files: []*std.MemFile{ + { + Name: fileName, + Body: body, + }, + }, + }, + Deposit: deposit, + } + + // Sponsoree creates a new sponsor transaction + tx, err := sponsoreeClient.NewSponsorTransaction(cfg, msg) + require.NoError(t, err) + + // Sponsoree signs the transaction + sponsorTx, err := sponsoreeClient.SignTransaction(*tx, sponsoreeAccountNumber, sponsoreeSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sponsoree + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "" + got := string(res.DeliverTx.Data) + + assert.Nil(t, err) + assert.Equal(t, expected, got) + + // Check for deployed file on the node + query, err := sponsorClient.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath), + }) + require.NoError(t, err) + assert.Equal(t, string(query.Response.Data), fileName) + + // Query package's balance to validate the deposit amount + baseAcc, _, err := sponsorClient.QueryAccount(gnolang.DerivePkgAddr(deploymentPath)) + require.NoError(t, err) + assert.Equal(t, baseAcc.GetCoins().String(), deposit) + + // Query sponsoree's balance after the transaction + sponsoreeAfter, _, err := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + require.NoError(t, err) + expectedSponsoreeAfter := sponsoreeBefore.GetCoins().Sub(std.MustParseCoins(deposit)) + assert.Equal(t, expectedSponsoreeAfter, sponsoreeAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) +} + func TestAddPackageMultiple_Integration(t *testing.T) { // Set up in-memory node config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) @@ -432,7 +1267,8 @@ func TestAddPackageMultiple_Integration(t *testing.T) { defer node.Stop() // Init Signer & RPCClient - signer := newInMemorySigner(t, "tendermint_test") + keybase := keys.NewInMemory() + signer := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) require.NoError(t, err) @@ -464,7 +1300,7 @@ func Echo(str string) string { body2 := `package hello func Hello(str string) string { - return "Hello " + str + "!" + return "Hello " + str + "!" }` msg1 := MsgAddPackage{ @@ -501,7 +1337,7 @@ func Hello(str string) string { // Execute AddPackage _, err = client.AddPackage(baseCfg, msg1, msg2) - assert.Nil(t, err) + assert.NoError(t, err) // Check Package #1 query, err := client.Query(QueryCfg{ @@ -531,24 +1367,208 @@ func Hello(str string) string { assert.Equal(t, baseAcc.GetCoins().String(), deposit) } +func TestAddPackageMultiple_Sponsor_Integration(t *testing.T) { + // Set up an in-memory node + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config) + defer node.Stop() + + // Initialize in-memory key storage + keybase := keys.NewInMemory() + + // Create signer accounts for sponsor and sponsoree + sponsor := newInMemorySigner(t, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name) + sponsoree := newInMemorySigner(t, keybase, generateMnemonic(t), "test2") + + sponsorInfo, err := sponsor.Info() + require.NoError(t, err) + + sponsoreeInfo, err := sponsoree.Info() + require.NoError(t, err) + + // Set up an RPC client to interact with the in-memory node + rpcClient, err := rpcclient.NewHTTPClient(remoteAddr) + require.NoError(t, err) + + // Initialize sponsor and sponsoree clients with their respective signers and RPC client + sponsorClient := Client{ + Signer: sponsor, + RPCClient: rpcClient, + } + + sponsoreeClient := Client{ + Signer: sponsoree, + RPCClient: rpcClient, + } + + // Ensure sponsoree has enough money to make msg addpackage + _, err = sponsorClient.Send(BaseTxCfg{ + GasWanted: 1000000, + GasFee: "100000ugnot", + Memo: "Test memo", + }, MsgSend{ + ToAddress: sponsoreeInfo.GetAddress(), + Send: "100000ugnot", + }) + require.NoError(t, err) + + // Fetch sponsoree account information before the transaction + var sponsoreeAccountNumber uint64 = 0 + var sponsoreeSequence uint64 = 0 + + sponsoreeBefore, _, _ := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + if sponsoreeBefore != nil { + sponsoreeAccountNumber = sponsoreeBefore.AccountNumber + sponsoreeSequence = sponsoreeBefore.Sequence + } + + // Configure the transaction to be sponsored + cfg := SponsorTxCfg{ + BaseTxCfg: BaseTxCfg{ + GasFee: "10000ugnot", + GasWanted: 8000000, + Memo: "", + }, + SponsorAddress: sponsorInfo.GetAddress(), + } + + deposit := "100ugnot" + deploymentPath1 := "gno.land/p/demo/integration/test/echo" + + body1 := `package echo + +func Echo(str string) string { + return str +}` + + deploymentPath2 := "gno.land/p/demo/integration/test/hello" + body2 := `package hello + +func Hello(str string) string { + return "Hello " + str + "!" +}` + + msg1 := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "echo", + Path: deploymentPath1, + Files: []*std.MemFile{ + { + Name: "echo.gno", + Body: body1, + }, + }, + }, + Deposit: "", + } + + msg2 := MsgAddPackage{ + Package: &std.MemPackage{ + Name: "hello", + Path: deploymentPath2, + Files: []*std.MemFile{ + { + Name: "gno.mod", + Body: "module gno.land/p/demo/integration/test/hello", + }, + { + Name: "hello.gno", + Body: body2, + }, + }, + }, + Deposit: deposit, + } + + // Sponsoree creates a new sponsor transaction + tx, err := sponsoreeClient.NewSponsorTransaction(cfg, msg1, msg2) + require.NoError(t, err) + + // Sponsoree signs the transaction + sponsorTx, err := sponsoreeClient.SignTransaction(*tx, sponsoreeAccountNumber, sponsoreeSequence) + require.NoError(t, err) + + // Fetch sponsor account information before the transaction + sponsorBefore, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + + // Sponsor executes the transaction which received from sponsoree + res, err := sponsorClient.ExecuteSponsorTransaction(*sponsorTx, sponsorBefore.AccountNumber, sponsorBefore.Sequence) + require.NoError(t, err) + + // Check the result of the transaction execution + expected := "" + got := string(res.DeliverTx.Data) + + assert.Nil(t, err) + assert.Equal(t, expected, got) + + // Check Package #1 + query, err := sponsorClient.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath1), + }) + require.NoError(t, err) + assert.Equal(t, string(query.Response.Data), "echo.gno") + + // Query package's balance to validate the deposit amount + baseAcc, _, err := sponsorClient.QueryAccount(gnolang.DerivePkgAddr(deploymentPath1)) + require.NoError(t, err) + assert.Equal(t, baseAcc.GetCoins().String(), "") + + // Check Package #2 + query, err = sponsorClient.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath2), + }) + require.NoError(t, err) + assert.Contains(t, string(query.Response.Data), "hello.gno") + assert.Contains(t, string(query.Response.Data), "gno.mod") + + // Query package's balance to validate the deposit amount + baseAcc, _, err = sponsorClient.QueryAccount(gnolang.DerivePkgAddr(deploymentPath2)) + require.NoError(t, err) + assert.Equal(t, baseAcc.GetCoins().String(), deposit) + + // Query sponsoree's balance after the transaction + sponsoreeAfter, _, err := sponsoreeClient.QueryAccount(sponsoreeInfo.GetAddress()) + require.NoError(t, err) + expectedSponsoreeAfter := sponsoreeBefore.GetCoins().Sub(std.MustParseCoins(deposit)) + assert.Equal(t, expectedSponsoreeAfter, sponsoreeAfter.GetCoins()) + + // Query sponsor's balance after the transaction + sponsorAfter, _, err := sponsorClient.QueryAccount(sponsorInfo.GetAddress()) + require.NoError(t, err) + expectedSponsorAfter := sponsorBefore.GetCoins().Sub(std.MustParseCoins(cfg.BaseTxCfg.GasFee)) + assert.Equal(t, expectedSponsorAfter, sponsorAfter.GetCoins()) +} + // todo add more integration tests: // MsgCall with Send field populated (single/multiple) // MsgRun with Send field populated (single/multiple) -func newInMemorySigner(t *testing.T, chainid string) *SignerFromKeybase { +func newInMemorySigner(t *testing.T, kb keys.Keybase, mnemonic, accName string) *SignerFromKeybase { t.Helper() - mnemonic := integration.DefaultAccount_Seed - name := integration.DefaultAccount_Name - - kb := keys.NewInMemory() - _, err := kb.CreateAccount(name, mnemonic, "", "", uint32(0), uint32(0)) + _, err := kb.CreateAccount(accName, mnemonic, "", "", uint32(0), uint32(0)) require.NoError(t, err) return &SignerFromKeybase{ - Keybase: kb, // Stores keys in memory or on disk - Account: name, // Account name or bech32 format - Password: "", // Password for encryption - ChainID: chainid, // Chain ID for transaction signing + Keybase: kb, // Stores keys in memory or on disk + Account: accName, // Account name or bech32 format + Password: "", // Password for encryption + ChainID: "tendermint_test", // Chain ID for transaction signing } } + +func generateMnemonic(t *testing.T) string { + t.Helper() + + entropy, err := bip39.NewEntropy(256) + require.NoError(t, err) + + mnemonic, err := bip39.NewMnemonic(entropy) + require.NoError(t, err) + + return mnemonic +} diff --git a/gno.land/pkg/gnoclient/mock_test.go b/gno.land/pkg/gnoclient/mock_test.go index 385eeb0916e..cba0d74d718 100644 --- a/gno.land/pkg/gnoclient/mock_test.go +++ b/gno.land/pkg/gnoclient/mock_test.go @@ -10,13 +10,22 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" ) -// Signer mock type ( + // Signer mock mockSign func(cfg SignCfg) (*std.Tx, error) - mockInfo func() keys.Info + mockInfo func() (keys.Info, error) mockValidate func() error + + // Msg mock + mockMsg struct{} ) +func (m mockMsg) validateMsg() error { return nil } +func (m mockMsg) getType() string { return "mock" } +func (m mockMsg) getCoins() (std.Coins, error) { + return std.NewCoins(std.MustParseCoin("1000ugnot")), nil +} + type mockSigner struct { sign mockSign info mockInfo @@ -30,11 +39,11 @@ func (m *mockSigner) Sign(cfg SignCfg) (*std.Tx, error) { return nil, nil } -func (m *mockSigner) Info() keys.Info { +func (m *mockSigner) Info() (keys.Info, error) { if m.info != nil { return m.info() } - return nil + return nil, nil } func (m *mockSigner) Validate() error { diff --git a/gno.land/pkg/gnoclient/signer.go b/gno.land/pkg/gnoclient/signer.go index f8e1e6b8522..3473ae26453 100644 --- a/gno.land/pkg/gnoclient/signer.go +++ b/gno.land/pkg/gnoclient/signer.go @@ -12,7 +12,7 @@ import ( // Signer provides an interface for signing transactions. type Signer interface { Sign(SignCfg) (*std.Tx, error) // Signs a transaction and returns a signed tx ready for broadcasting. - Info() keys.Info // Returns key information, including the address. + Info() (keys.Info, error) // Returns key information, including the address. Validate() error // Checks whether the signer is properly configured. } @@ -24,6 +24,9 @@ type SignerFromKeybase struct { ChainID string // Chain ID for transaction signing } +// Ensure SignerFromKeybase implements the Signer interface. +var _ Signer = (*SignerFromKeybase)(nil) + // Validate checks if the signer is properly configured. func (s SignerFromKeybase) Validate() error { if s.ChainID == "" { @@ -35,12 +38,17 @@ func (s SignerFromKeybase) Validate() error { return err } + caller, err := s.Info() + if err != nil { + return err + } + // To verify if the password unlocks the account, sign a blank transaction. msg := vm.MsgCall{ - Caller: s.Info().GetAddress(), + Caller: caller.GetAddress(), } signCfg := SignCfg{ - UnsignedTX: std.Tx{ + Tx: std.Tx{ Msgs: []std.Msg{msg}, Fee: std.NewFee(0, std.NewCoin("ugnot", 1000000)), }, @@ -53,25 +61,25 @@ func (s SignerFromKeybase) Validate() error { } // Info gets keypair information. -func (s SignerFromKeybase) Info() keys.Info { +func (s SignerFromKeybase) Info() (keys.Info, error) { info, err := s.Keybase.GetByNameOrAddress(s.Account) if err != nil { - panic("should not happen") + return nil, err } - return info + return info, nil } // SignCfg provides the signing configuration, containing: // unsigned transaction data, account number, and account sequence. type SignCfg struct { - UnsignedTX std.Tx + Tx std.Tx SequenceNumber uint64 AccountNumber uint64 } // Sign implements the Signer interface for SignerFromKeybase. func (s SignerFromKeybase) Sign(cfg SignCfg) (*std.Tx, error) { - tx := cfg.UnsignedTX + tx := cfg.Tx chainID := s.ChainID accountNumber := cfg.AccountNumber sequenceNumber := cfg.SequenceNumber @@ -105,7 +113,9 @@ func (s SignerFromKeybase) Sign(cfg SignCfg) (*std.Tx, error) { if err != nil { return nil, err } + addr := pub.Address() + found := false for i := range tx.Signatures { if signers[i] == addr { @@ -124,9 +134,6 @@ func (s SignerFromKeybase) Sign(cfg SignCfg) (*std.Tx, error) { return &tx, nil } -// Ensure SignerFromKeybase implements the Signer interface. -var _ Signer = (*SignerFromKeybase)(nil) - // SignerFromBip39 creates a signer from an in-memory keybase with a single default account, derived from the given mnemonic. // This can be useful in scenarios where storing private keys in the filesystem isn't feasible. // diff --git a/gno.land/pkg/gnoclient/signer_test.go b/gno.land/pkg/gnoclient/signer_test.go index 3b4cbb757ad..bf744ed84c4 100644 --- a/gno.land/pkg/gnoclient/signer_test.go +++ b/gno.land/pkg/gnoclient/signer_test.go @@ -1 +1,161 @@ package gnoclient + +import ( + "testing" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSignerFromBip39 tests the SignerFromBip39 function. +func TestSignerFromBip39(t *testing.T) { + t.Parallel() + + chainID := "test-chain-id" + passphrase := "" + account := uint32(0) + index := uint32(0) + + // Define test cases with mnemonic and expected outcomes. + testcases := []struct { + name string + mnemonic string + expectedError bool + }{ + { + name: "Valid mnemonic", + mnemonic: "index brass unknown lecture autumn provide royal shrimp elegant wink now zebra discover swarm act ill you bullet entire outdoor tilt usage gap multiply", + expectedError: false, + }, + { + name: "Invalid mnemonic", + mnemonic: "invalid mnemonic", + expectedError: true, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Create a signer from mnemonic + signer, err := SignerFromBip39(tc.mnemonic, chainID, passphrase, account, index) + + // Check if an error was expected + if tc.expectedError { + assert.Error(t, err) + assert.Nil(t, signer) + } else { + require.NoError(t, err) + require.NotNil(t, signer) + + // Validate the signer + err = signer.Validate() + assert.NoError(t, err) + } + }) + } +} + +// TestSignerFromKeybase tests the SignerFromKeybase struct. +func TestSignerFromKeybase(t *testing.T) { + t.Parallel() + + chainID := "test-chain-id" + passphrase := "" + account := uint32(0) + index := uint32(0) + + mnemonic := "index brass unknown lecture autumn provide royal shrimp elegant wink now zebra discover swarm act ill you bullet entire outdoor tilt usage gap multiply" + + // Define test cases for different scenarios of the signer + tests := []struct { + name string + account string + password string + expectedError bool + validateOnly bool + }{ + { + name: "Valid signer", + account: "default", + password: "", + expectedError: false, + }, + { + name: "Missing ChainID", + account: "default", + password: "", + expectedError: true, + validateOnly: true, + }, + { + name: "Incorrect password", + account: "default", + password: "wrong-password", + expectedError: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() // Run tests in parallel + + // Initialize in-memory keybase and create account + kb := keys.NewInMemory() + name := "default" + password := "" + + _, err := kb.CreateAccount(name, mnemonic, passphrase, password, account, index) + require.NoError(t, err) + + // Create a signer from the keybase + signer := SignerFromKeybase{ + Keybase: kb, + Account: tc.account, + Password: tc.password, + ChainID: chainID, + } + + signerInfo, err := signer.Info() + require.NoError(t, err) + + // Test for missing ChainID scenario + if tc.validateOnly { + signer.ChainID = "" + err := signer.Validate() + assert.Error(t, err) + assert.Equal(t, "missing ChainID", err.Error()) + } else { + // Prepare a sign configuration + signCfg := SignCfg{ + Tx: std.Tx{ + Msgs: []std.Msg{ + vm.MsgCall{ + Caller: signerInfo.GetAddress(), + }, + }, + Fee: std.NewFee(0, std.NewCoin("ugnot", 1000000)), + }, + } + + // Try to sign the transaction + signedTx, err := signer.Sign(signCfg) + + // Check if an error was expected + if tc.expectedError { + assert.Error(t, err) + assert.Nil(t, signedTx) + } else { + assert.NoError(t, err) + assert.NotNil(t, signedTx) + } + } + }) + } +} diff --git a/gno.land/pkg/gnoclient/util.go b/gno.land/pkg/gnoclient/util.go index 177e6d92906..5a9f677a37a 100644 --- a/gno.land/pkg/gnoclient/util.go +++ b/gno.land/pkg/gnoclient/util.go @@ -1,7 +1,56 @@ package gnoclient -import "github.com/gnolang/gno/tm2/pkg/std" +import ( + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/std" +) +// Define various error messages for different validation failures +var ( + ErrEmptyPackage = errors.New("empty package to run") + ErrEmptyPkgPath = errors.New("empty pkg path") + ErrEmptyFuncName = errors.New("empty function name") + ErrInvalidGasWanted = errors.New("invalid gas wanted") + ErrInvalidGasFee = errors.New("invalid gas fee") + ErrMissingSigner = errors.New("missing Signer") + ErrMissingRPCClient = errors.New("missing RPCClient") + ErrInvalidToAddress = errors.New("invalid send to address") + ErrInvalidAmount = errors.New("invalid send/deposit amount") + ErrInvalidMsgType = errors.New("invalid msg type") + ErrNoMessages = errors.New("no messages provided") + ErrMixedMessageTypes = errors.New("mixed message types not allowed") + ErrNoSignatures = errors.New("no signatures provided") + + ErrInvalidSponsorAddress = errors.New("invalid sponsor address") + ErrInvalidSponsorTx = errors.New("invalid sponsor tx") +) + +// Constants for different message types. +const ( + MSG_CALL = "call" + MSG_RUN = "run" + MSG_SEND = "send" + MSG_ADD_PKG = "add_pkg" +) + +// Msg defines the interface for different types of messages. +type Msg interface { + validateMsg() error // Validates the message. + getCoins() (std.Coins, error) // Retrieves the coins involved in the message. + getType() string // Returns the type of the message. +} + +// BaseTxCfg defines the base transaction configuration shared by all message types. +type BaseTxCfg struct { + GasFee string // Gas fee + GasWanted int64 // Gas wanted + AccountNumber uint64 // Account number + SequenceNumber uint64 // Sequence number + Memo string // Memo +} + +// validateBaseTxConfig validates the base transaction configuration. func (cfg BaseTxCfg) validateBaseTxConfig() error { if cfg.GasWanted <= 0 { return ErrInvalidGasWanted @@ -9,45 +58,144 @@ func (cfg BaseTxCfg) validateBaseTxConfig() error { if cfg.GasFee == "" { return ErrInvalidGasFee } + return nil +} + +type SponsorTxCfg struct { + BaseTxCfg + SponsorAddress crypto.Address +} +// validateBaseTxConfig validates the base transaction configuration. +func (cfg SponsorTxCfg) validateSponsorTxConfig() error { + if cfg.SponsorAddress.IsZero() { + return ErrInvalidSponsorAddress + } + if cfg.GasWanted <= 0 { + return ErrInvalidGasWanted + } + if cfg.GasFee == "" { + return ErrInvalidGasFee + } return nil } -func (msg MsgCall) validateMsgCall() error { +// MsgCall represents a call message in the VM. +type MsgCall struct { + PkgPath string // Package path + FuncName string // Function name + Args []string // Function arguments + Send string // Send amount +} + +// getType returns the type of the MsgCall. +func (msg MsgCall) getType() string { + return MSG_CALL +} + +// validateMsg validates the MsgCall. +func (msg MsgCall) validateMsg() error { if msg.PkgPath == "" { return ErrEmptyPkgPath } if msg.FuncName == "" { return ErrEmptyFuncName } - return nil } -func (msg MsgSend) validateMsgSend() error { +// getCoins retrieves the coins involved in the MsgCall. +func (msg MsgCall) getCoins() (std.Coins, error) { + coins, err := std.ParseCoins(msg.Send) + if err != nil { + return nil, ErrInvalidAmount + } + return coins, nil +} + +// MsgSend represents a send message in the banker. +type MsgSend struct { + ToAddress crypto.Address // Send to address + Send string // Send amount +} + +// getType returns the type of the MsgSend. +func (msg MsgSend) getType() string { + return MSG_SEND +} + +// validateMsg validates the MsgSend. +func (msg MsgSend) validateMsg() error { if msg.ToAddress.IsZero() { return ErrInvalidToAddress } - _, err := std.ParseCoins(msg.Send) + if _, err := std.ParseCoins(msg.Send); err != nil { + return ErrInvalidAmount + } + return nil +} + +// getCoins retrieves the coins involved in the MsgSend. +func (msg MsgSend) getCoins() (std.Coins, error) { + coins, err := std.ParseCoins(msg.Send) if err != nil { - return ErrInvalidSendAmount + return nil, ErrInvalidAmount } + return coins, nil +} - return nil +// MsgRun represents a run message in the VM. +type MsgRun struct { + Package *std.MemPackage // Package to run + Send string // Send amount } -func (msg MsgRun) validateMsgRun() error { +// getType returns the type of the MsgRun. +func (msg MsgRun) getType() string { + return MSG_RUN +} + +// validateMsg validates the MsgRun. +func (msg MsgRun) validateMsg() error { if msg.Package == nil || len(msg.Package.Files) == 0 { return ErrEmptyPackage } - return nil } -func (msg MsgAddPackage) validateMsgAddPackage() error { +// getCoins retrieves the coins involved in the MsgRun. +func (msg MsgRun) getCoins() (std.Coins, error) { + coins, err := std.ParseCoins(msg.Send) + if err != nil { + return nil, ErrInvalidAmount + } + return coins, nil +} + +// MsgAddPackage represents an add package message in the VM. +type MsgAddPackage struct { + Package *std.MemPackage // Package to add + Deposit string // Coin deposit +} + +// getType returns the type of the MsgAddPackage. +func (msg MsgAddPackage) getType() string { + return MSG_ADD_PKG +} + +// validateMsg validates the MsgAddPackage. +func (msg MsgAddPackage) validateMsg() error { if msg.Package == nil || len(msg.Package.Files) == 0 { return ErrEmptyPackage } - return nil } + +// getCoins retrieves the coins involved in the MsgAddPackage. +func (msg MsgAddPackage) getCoins() (std.Coins, error) { + coins, err := std.ParseCoins(msg.Deposit) + if err != nil { + return nil, ErrInvalidAmount + } + return coins, nil +} diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index cd0c946f907..f4d353411f8 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -4,12 +4,15 @@ import ( "fmt" "log/slog" "path/filepath" + "strconv" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" @@ -32,7 +35,13 @@ type AppOptions struct { GnoRootDir string GenesisTxHandler GenesisTxHandler Logger *slog.Logger + EventSwitch events.EventSwitch MaxCycles int64 + // Whether to cache the result of loading the standard libraries. + // This is useful if you have to start many nodes, like in testing. + // This disables loading existing packages; so it should only be used + // on a fresh database. + CacheStdlibLoad bool } func NewAppOptions() *AppOptions { @@ -41,6 +50,7 @@ func NewAppOptions() *AppOptions { Logger: log.NewNoopLogger(), DB: memdb.NewMemDB(), GnoRootDir: gnoenv.RootDir(), + EventSwitch: events.NilEventSwitch(), } } @@ -56,7 +66,7 @@ func (c *AppOptions) validate() error { return nil } -// NewApp creates the GnoLand application. +// NewAppWithOptions creates the GnoLand application with specified options func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { if err := cfg.validate(); err != nil { return nil, err @@ -81,7 +91,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // XXX: Embed this ? stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") - vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) + vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) // Set InitChainer baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.GenesisTxHandler)) @@ -106,13 +116,25 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { }, ) + // Set up the event collector + c := newCollector[validatorUpdate]( + cfg.EventSwitch, // global event switch filled by the node + validatorEventFilter, // filter fn that keeps the collector valid + ) + // Set EndBlocker - baseApp.SetEndBlocker(EndBlocker(vmKpr)) + baseApp.SetEndBlocker( + EndBlocker( + c, + vmk, + baseApp, + ), + ) // Set a handler Route. baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) baseApp.Router().AddRoute("bank", bank.NewHandler(bankKpr)) - baseApp.Router().AddRoute("vm", vm.NewHandler(vmKpr)) + baseApp.Router().AddRoute("vm", vm.NewHandler(vmk)) // Load latest version. if err := baseApp.LoadLatestVersion(); err != nil { @@ -120,13 +142,20 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { } // Initialize the VMKeeper. - vmKpr.Initialize(baseApp.GetCacheMultiStore()) + ms := baseApp.GetCacheMultiStore() + vmk.Initialize(cfg.Logger, ms, cfg.CacheStdlibLoad) + ms.MultiWrite() // XXX why was't this needed? return baseApp, nil } // NewApp creates the GnoLand application. -func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, maxCycles int64) (abci.Application, error) { +func NewApp( + dataRootDir string, + skipFailingGenesisTxs bool, + evsw events.EventSwitch, + logger *slog.Logger, +) (abci.Application, error) { var err error cfg := NewAppOptions() @@ -141,59 +170,175 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, } cfg.Logger = logger + cfg.EventSwitch = evsw + return NewAppWithOptions(cfg) } type GenesisTxHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result) -func NoopGenesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {} +func NoopGenesisTxHandler(_ sdk.Context, _ std.Tx, _ sdk.Result) {} -func PanicOnFailingTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { +func PanicOnFailingTxHandler(_ sdk.Context, _ std.Tx, res sdk.Result) { if res.IsErr() { panic(res.Log) } } // InitChainer returns a function that can initialize the chain with genesis. -func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, resHandler GenesisTxHandler) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { +func InitChainer( + baseApp *sdk.BaseApp, + acctKpr auth.AccountKeeperI, + bankKpr bank.BankKeeperI, + resHandler GenesisTxHandler, +) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - // Get genesis state. - genState := req.AppState.(GnoGenesisState) - // Parse and set genesis state balances. - for _, bal := range genState.Balances { - acc := acctKpr.NewAccountWithAddress(ctx, bal.Address) - acctKpr.SetAccount(ctx, acc) - err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount) - if err != nil { - panic(err) + txResponses := []abci.ResponseDeliverTx{} + + if req.AppState != nil { + // Get genesis state + genState := req.AppState.(GnoGenesisState) + + // Parse and set genesis state balances + for _, bal := range genState.Balances { + acc := acctKpr.NewAccountWithAddress(ctx, bal.Address) + acctKpr.SetAccount(ctx, acc) + err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount) + if err != nil { + panic(err) + } } - } - // Run genesis txs. - for _, tx := range genState.Txs { - res := baseApp.Deliver(tx) - if res.IsErr() { - ctx.Logger().Error( - "Unable to deliver genesis tx", - "log", res.Log, - "error", res.Error, - "gas-used", res.GasUsed, - ) + // Run genesis txs + for _, tx := range genState.Txs { + res := baseApp.Deliver(tx) + if res.IsErr() { + ctx.Logger().Error( + "Unable to deliver genesis tx", + "log", res.Log, + "error", res.Error, + "gas-used", res.GasUsed, + ) + } + + txResponses = append(txResponses, abci.ResponseDeliverTx{ + ResponseBase: res.ResponseBase, + GasWanted: res.GasWanted, + GasUsed: res.GasUsed, + }) + + resHandler(ctx, tx, res) } - - resHandler(ctx, tx, res) } // Done! return abci.ResponseInitChain{ - Validators: req.Validators, + Validators: req.Validators, + TxResponses: txResponses, } } } -// XXX not used yet. -func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return abci.ResponseEndBlock{} +// endBlockerApp is the app abstraction required by any EndBlocker +type endBlockerApp interface { + // LastBlockHeight returns the latest app height + LastBlockHeight() int64 + + // Logger returns the logger reference + Logger() *slog.Logger +} + +// EndBlocker defines the logic executed after every block. +// Currently, it parses events that happened during execution to calculate +// validator set changes +func EndBlocker( + collector *collector[validatorUpdate], + vmk vm.VMKeeperI, + app endBlockerApp, +) func( + ctx sdk.Context, + req abci.RequestEndBlock, +) abci.ResponseEndBlock { + return func(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { + // Check if there was a valset change + if len(collector.getEvents()) == 0 { + // No valset updates + return abci.ResponseEndBlock{} + } + + // Run the VM to get the updates from the chain + response, err := vmk.QueryEval( + ctx, + valRealm, + fmt.Sprintf("%s(%d)", valChangesFn, app.LastBlockHeight()), + ) + if err != nil { + app.Logger().Error("unable to call VM during EndBlocker", "err", err) + + return abci.ResponseEndBlock{} + } + + // Extract the updates from the VM response + updates, err := extractUpdatesFromResponse(response) + if err != nil { + app.Logger().Error("unable to extract updates from response", "err", err) + + return abci.ResponseEndBlock{} + } + + return abci.ResponseEndBlock{ + ValidatorUpdates: updates, + } + } +} + +// extractUpdatesFromResponse extracts the validator set updates +// from the VM response. +// +// This method is not ideal, but currently there is no mechanism +// in place to parse typed VM responses +func extractUpdatesFromResponse(response string) ([]abci.ValidatorUpdate, error) { + // Find the submatches + matches := valRegexp.FindAllStringSubmatch(response, -1) + if len(matches) == 0 { + // No changes to extract + return nil, nil + } + + updates := make([]abci.ValidatorUpdate, 0, len(matches)) + for _, match := range matches { + var ( + addressRaw = match[1] + pubKeyRaw = match[2] + powerRaw = match[3] + ) + + // Parse the address + address, err := crypto.AddressFromBech32(addressRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse address, %w", err) + } + + // Parse the public key + pubKey, err := crypto.PubKeyFromBech32(pubKeyRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse public key, %w", err) + } + + // Parse the voting power + power, err := strconv.ParseInt(powerRaw, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse voting power, %w", err) + } + + update := abci.ValidatorUpdate{ + Address: address, + PubKey: pubKey, + Power: power, + } + + updates = append(updates, update) } + + return updates, nil } diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go new file mode 100644 index 00000000000..852d090f3af --- /dev/null +++ b/gno.land/pkg/gnoland/app_test.go @@ -0,0 +1,257 @@ +package gnoland + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/gnolang/gno/gnovm/stdlibs/std" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateValidatorUpdates generates dummy validator updates +func generateValidatorUpdates(t *testing.T, count int) []abci.ValidatorUpdate { + t.Helper() + + validators := make([]abci.ValidatorUpdate, 0, count) + + for i := 0; i < count; i++ { + // Generate a random private key + key := getDummyKey(t) + + validator := abci.ValidatorUpdate{ + Address: key.Address(), + PubKey: key, + Power: 1, + } + + validators = append(validators, validator) + } + + return validators +} + +func TestEndBlocker(t *testing.T) { + t.Parallel() + + constructVMResponse := func(updates []abci.ValidatorUpdate) string { + var builder strings.Builder + + builder.WriteString("(slice[") + + for i, update := range updates { + builder.WriteString( + fmt.Sprintf( + "(struct{(%q std.Address),(%q string),(%d uint64)} gno.land/p/sys/validators.Validator)", + update.Address, + update.PubKey, + update.Power, + ), + ) + + if i < len(updates)-1 { + builder.WriteString(",") + } + } + + builder.WriteString("] []gno.land/p/sys/validators.Validator)") + + return builder.String() + } + + newCommonEvSwitch := func() *mockEventSwitch { + var cb events.EventCallback + + return &mockEventSwitch{ + addListenerFn: func(_ string, callback events.EventCallback) { + cb = callback + }, + fireEventFn: func(event events.Event) { + cb(event) + }, + } + } + + t.Run("no collector events", func(t *testing.T) { + t.Parallel() + + noFilter := func(e events.Event) []validatorUpdate { + return []validatorUpdate{} + } + + // Create the collector + c := newCollector[validatorUpdate](&mockEventSwitch{}, noFilter) + + // Create the EndBlocker + eb := EndBlocker(c, nil, &mockEndBlockerApp{}) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was empty + assert.Equal(t, abci.ResponseEndBlock{}, res) + }) + + t.Run("invalid VM call", func(t *testing.T) { + t.Parallel() + + var ( + noFilter = func(e events.Event) []validatorUpdate { + return make([]validatorUpdate, 1) // 1 update + } + + vmCalled bool + + mockEventSwitch = newCommonEvSwitch() + + mockVMKeeper = &mockVMKeeper{ + queryFn: func(_ sdk.Context, pkgPath, expr string) (string, error) { + vmCalled = true + + require.Equal(t, valRealm, pkgPath) + require.NotEmpty(t, expr) + + return "", errors.New("random call error") + }, + } + ) + + // Create the collector + c := newCollector[validatorUpdate](mockEventSwitch, noFilter) + + // Fire a GnoVM event + mockEventSwitch.FireEvent(std.GnoEvent{}) + + // Create the EndBlocker + eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was empty + assert.Equal(t, abci.ResponseEndBlock{}, res) + + // Make sure the VM was called + assert.True(t, vmCalled) + }) + + t.Run("empty VM response", func(t *testing.T) { + t.Parallel() + + var ( + noFilter = func(e events.Event) []validatorUpdate { + return make([]validatorUpdate, 1) // 1 update + } + + vmCalled bool + + mockEventSwitch = newCommonEvSwitch() + + mockVMKeeper = &mockVMKeeper{ + queryFn: func(_ sdk.Context, pkgPath, expr string) (string, error) { + vmCalled = true + + require.Equal(t, valRealm, pkgPath) + require.NotEmpty(t, expr) + + return constructVMResponse([]abci.ValidatorUpdate{}), nil + }, + } + ) + + // Create the collector + c := newCollector[validatorUpdate](mockEventSwitch, noFilter) + + // Fire a GnoVM event + mockEventSwitch.FireEvent(std.GnoEvent{}) + + // Create the EndBlocker + eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was empty + assert.Equal(t, abci.ResponseEndBlock{}, res) + + // Make sure the VM was called + assert.True(t, vmCalled) + }) + + t.Run("multiple valset updates", func(t *testing.T) { + t.Parallel() + + var ( + changes = generateValidatorUpdates(t, 100) + + mockEventSwitch = newCommonEvSwitch() + + mockVMKeeper = &mockVMKeeper{ + queryFn: func(_ sdk.Context, pkgPath, expr string) (string, error) { + require.Equal(t, valRealm, pkgPath) + require.NotEmpty(t, expr) + + return constructVMResponse(changes), nil + }, + } + ) + + // Create the collector + c := newCollector[validatorUpdate](mockEventSwitch, validatorEventFilter) + + // Construct the GnoVM events + vmEvents := make([]abci.Event, 0, len(changes)) + for index := range changes { + event := std.GnoEvent{ + Type: validatorAddedEvent, + PkgPath: valRealm, + } + + // Make half the changes validator removes + if index%2 == 0 { + changes[index].Power = 0 + + event = std.GnoEvent{ + Type: validatorRemovedEvent, + PkgPath: valRealm, + } + } + + vmEvents = append(vmEvents, event) + } + + // Fire the tx result event + txEvent := types.EventTx{ + Result: types.TxResult{ + Response: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Events: vmEvents, + }, + }, + }, + } + + mockEventSwitch.FireEvent(txEvent) + + // Create the EndBlocker + eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was not empty + require.Len(t, res.ValidatorUpdates, len(changes)) + + for index, update := range res.ValidatorUpdates { + assert.Equal(t, changes[index].Address, update.Address) + assert.True(t, changes[index].PubKey.Equals(update.PubKey)) + assert.Equal(t, changes[index].Power, update.Power) + } + }) +} diff --git a/gno.land/pkg/gnoland/events.go b/gno.land/pkg/gnoland/events.go new file mode 100644 index 00000000000..ba78c40979e --- /dev/null +++ b/gno.land/pkg/gnoland/events.go @@ -0,0 +1,51 @@ +package gnoland + +import ( + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/rs/xid" +) + +// filterFn is the filter method for incoming events +type filterFn[T any] func(events.Event) []T + +// collector is the generic in-memory event collector +type collector[T any] struct { + events []T // temporary event storage + filter filterFn[T] // method used for filtering events +} + +// newCollector creates a new event collector +func newCollector[T any]( + evsw events.EventSwitch, + filter filterFn[T], +) *collector[T] { + c := &collector[T]{ + events: make([]T, 0), + filter: filter, + } + + // Register the listener + evsw.AddListener(xid.New().String(), func(e events.Event) { + c.updateWith(e) + }) + + return c +} + +// updateWith updates the collector with the given event +func (c *collector[T]) updateWith(event events.Event) { + if extracted := c.filter(event); extracted != nil { + c.events = append(c.events, extracted...) + } +} + +// getEvents returns the filtered events, +// and resets the collector store +func (c *collector[T]) getEvents() []T { + capturedEvents := make([]T, len(c.events)) + copy(capturedEvents, c.events) + + c.events = c.events[:0] + + return capturedEvents +} diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index d99dfe7e5ed..f5f0aa56758 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -86,7 +86,7 @@ func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]st // LoadPackagesFromDir loads gno packages from a directory. // It creates and returns a list of transactions based on these packages. -func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { +func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]std.Tx, error) { // list all packages from target path pkgs, err := gnomod.ListPkgs(dir) if err != nil { @@ -103,7 +103,7 @@ func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit s nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() txs := []std.Tx{} for _, pkg := range nonDraftPkgs { - tx, err := LoadPackage(pkg, creator, fee, deposit) + tx, err := LoadPackage(pkg, creator, fee, nil) if err != nil { return nil, fmt.Errorf("unable to load package %q: %w", pkg.Dir, err) } diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go new file mode 100644 index 00000000000..1ff9f168bd1 --- /dev/null +++ b/gno.land/pkg/gnoland/mock_test.go @@ -0,0 +1,118 @@ +package gnoland + +import ( + "log/slog" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "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/service" +) + +type ( + fireEventDelegate func(events.Event) + addListenerDelegate func(string, events.EventCallback) + removeListenerDelegate func(string) +) + +type mockEventSwitch struct { + service.BaseService + + fireEventFn fireEventDelegate + addListenerFn addListenerDelegate + removeListenerFn removeListenerDelegate +} + +func (m *mockEventSwitch) FireEvent(ev events.Event) { + if m.fireEventFn != nil { + m.fireEventFn(ev) + } +} + +func (m *mockEventSwitch) AddListener( + listenerID string, + cb events.EventCallback, +) { + if m.addListenerFn != nil { + m.addListenerFn(listenerID, cb) + } +} + +func (m *mockEventSwitch) RemoveListener(listenerID string) { + if m.removeListenerFn != nil { + m.removeListenerFn(listenerID) + } +} + +type ( + addPackageDelegate func(sdk.Context, vm.MsgAddPackage) error + callDelegate func(sdk.Context, vm.MsgCall) (string, error) + queryEvalDelegate func(sdk.Context, string, string) (string, error) + runDelegate func(sdk.Context, vm.MsgRun) (string, error) +) + +type mockVMKeeper struct { + addPackageFn addPackageDelegate + callFn callDelegate + queryFn queryEvalDelegate + runFn runDelegate +} + +func (m *mockVMKeeper) AddPackage(ctx sdk.Context, msg vm.MsgAddPackage) error { + if m.addPackageFn != nil { + return m.addPackageFn(ctx, msg) + } + + return nil +} + +func (m *mockVMKeeper) Call(ctx sdk.Context, msg vm.MsgCall) (res string, err error) { + if m.callFn != nil { + return m.callFn(ctx, msg) + } + + return "", nil +} + +func (m *mockVMKeeper) QueryEval(ctx sdk.Context, pkgPath, expr string) (res string, err error) { + if m.queryFn != nil { + return m.queryFn(ctx, pkgPath, expr) + } + + return "", nil +} + +func (m *mockVMKeeper) Run(ctx sdk.Context, msg vm.MsgRun) (res string, err error) { + if m.runFn != nil { + return m.runFn(ctx, msg) + } + + return "", nil +} + +type ( + lastBlockHeightDelegate func() int64 + loggerDelegate func() *slog.Logger +) + +type mockEndBlockerApp struct { + lastBlockHeightFn lastBlockHeightDelegate + loggerFn loggerDelegate +} + +func (m *mockEndBlockerApp) LastBlockHeight() int64 { + if m.lastBlockHeightFn != nil { + return m.lastBlockHeightFn() + } + + return 0 +} + +func (m *mockEndBlockerApp) Logger() *slog.Logger { + if m.loggerFn != nil { + return m.loggerFn() + } + + return log.NewNoopLogger() +} diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index b7fe1161605..02691f89c3e 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -3,7 +3,6 @@ package gnoland import ( "fmt" "log/slog" - "sync" "time" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -11,7 +10,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/proxy" bft "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" @@ -33,8 +31,8 @@ func NewMockedPrivValidator() bft.PrivValidator { return bft.NewMockPVWithParams(ed25519.GenPrivKey(), false, false) } -// NewInMemoryNodeConfig creates a default configuration for an in-memory node. -func NewDefaultGenesisConfig(pk crypto.PubKey, chainid string) *bft.GenesisDoc { +// NewDefaultGenesisConfig creates a default configuration for an in-memory node. +func NewDefaultGenesisConfig(chainid string) *bft.GenesisDoc { return &bft.GenesisDoc{ GenesisTime: time.Now(), ChainID: chainid, @@ -87,6 +85,8 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, return nil, fmt.Errorf("validate config error: %w", err) } + evsw := events.NewEventSwitch() + // Initialize the application with the provided options gnoApp, err := NewAppWithOptions(&AppOptions{ Logger: logger, @@ -94,6 +94,8 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, GenesisTxHandler: cfg.GenesisTxHandler, MaxCycles: cfg.GenesisMaxVMCycles, DB: memdb.NewMemDB(), + EventSwitch: evsw, + CacheStdlibLoad: true, }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) @@ -114,8 +116,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, dbProvider := func(*node.DBContext) (db.DB, error) { return memdb.NewMemDB(), nil } - // generate p2p node identity - // XXX: do we need to configur + // Generate p2p node identity nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} // Create and return the in-memory node instance @@ -124,32 +125,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, appClientCreator, genProvider, dbProvider, + evsw, logger, ) } - -// GetNodeReadiness waits until the node is ready, signaling via the EventNewBlock event. -// XXX: This should be replace by https://github.com/gnolang/gno/pull/1216 -func GetNodeReadiness(n *node.Node) <-chan struct{} { - const listenerID = "first_block_listener" - - var once sync.Once - - nb := make(chan struct{}) - ready := func() { - close(nb) - n.EventSwitch().RemoveListener(listenerID) - } - - n.EventSwitch().AddListener(listenerID, func(ev events.Event) { - if _, ok := ev.(bft.EventNewBlock); ok { - once.Do(ready) - } - }) - - if n.BlockStore().Height() > 0 { - once.Do(ready) - } - - return nb -} diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go new file mode 100644 index 00000000000..1843dff3984 --- /dev/null +++ b/gno.land/pkg/gnoland/vals.go @@ -0,0 +1,61 @@ +package gnoland + +import ( + "regexp" + + gnovm "github.com/gnolang/gno/gnovm/stdlibs/std" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" +) + +const ( + valRealm = "gno.land/r/sys/validators" + valChangesFn = "GetChanges" + + validatorAddedEvent = "ValidatorAdded" + validatorRemovedEvent = "ValidatorRemoved" +) + +// XXX: replace with amino-based clean approach +var valRegexp = regexp.MustCompile(`{\("([^"]*)"\s[^)]+\),\("((?:[^"]|\\")*)"\s[^)]+\),\((\d+)\s[^)]+\)}`) + +// validatorUpdate is a type being used for "notifying" +// that a validator change happened on-chain. The events from `r/sys/validators` +// do not pass data related to validator add / remove instances (who, what, how) +type validatorUpdate struct{} + +// validatorEventFilter filters the given event to determine if it +// is tied to a validator update +func validatorEventFilter(event events.Event) []validatorUpdate { + // Make sure the event is a new TX event + txResult, ok := event.(types.EventTx) + if !ok { + return nil + } + + // Make sure an add / remove event happened + for _, ev := range txResult.Result.Response.Events { + // Make sure the event is a GnoVM event + gnoEv, ok := ev.(gnovm.GnoEvent) + if !ok { + continue + } + + // Make sure the event is from `r/sys/validators` + if gnoEv.PkgPath != valRealm { + continue + } + + // Make sure the event is either an add / remove + switch gnoEv.Type { + case validatorAddedEvent, validatorRemovedEvent: + // We don't pass data around with the events, but a single + // notification is enough to "trigger" a VM scrape + return []validatorUpdate{{}} + default: + continue + } + } + + return nil +} diff --git a/gno.land/pkg/gnoweb/gnoweb.go b/gno.land/pkg/gnoweb/gnoweb.go index 69a3e4f8f9a..b997de7840d 100644 --- a/gno.land/pkg/gnoweb/gnoweb.go +++ b/gno.land/pkg/gnoweb/gnoweb.go @@ -9,6 +9,7 @@ import ( "io/fs" "log/slog" "net/http" + "net/url" "os" "path/filepath" "runtime" @@ -88,8 +89,8 @@ func MakeApp(logger *slog.Logger, cfg Config) gotuna.App { "/testnets": "/r/gnoland/pages:p/testnets", "/start": "/r/gnoland/pages:p/start", "/license": "/r/gnoland/pages:p/license", - "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm - "/events": "/r/gnoland/pages:p/events", // XXX: replace with events realm + "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm + "/events": "/r/gnoland/events", } for from, to := range aliases { @@ -103,13 +104,14 @@ func MakeApp(logger *slog.Logger, cfg Config) gotuna.App { "/grants": "/partners", "/language": "/gnolang", "/getting-started": "/start", + "/gophercon24": "https://docs.gno.land", } for from, to := range redirects { app.Router.Handle(from, handlerRedirect(logger, app, &cfg, to)) } // realm routes // NOTE: see rePathPart. - app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt)$)?}", handlerRealmFile(logger, app, &cfg)) + app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:(?:.*\\.(?:gno|md|txt|mod)$)|(?:LICENSE$))?}", handlerRealmFile(logger, app, &cfg)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(logger, app, &cfg)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(logger, app, &cfg)) app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(logger, app, &cfg)) @@ -124,7 +126,7 @@ func MakeApp(logger *slog.Logger, cfg Config) gotuna.App { app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := r.RequestURI - handleNotFound(app, &cfg, path, w, r) + handleNotFound(logger, app, &cfg, path, w, r) }) return app } @@ -148,7 +150,7 @@ func handlerRealmAlias(logger *slog.Logger, app gotuna.App, cfg *Config, rlmpath } rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/") qpath := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr)) + data := []byte(fmt.Sprintf("%s:%s", rlmfullpath, querystr)) res, err := makeRequest(logger, cfg, qpath, data) if err != nil { writeError(logger, w, fmt.Errorf("gnoweb failed to query gnoland: %w", err)) @@ -321,7 +323,7 @@ func handleRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config, w http. return } qpath := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", rlmpath, querystr)) + data := []byte(fmt.Sprintf("%s:%s", rlmpath, querystr)) res, err := makeRequest(logger, cfg, qpath, data) if err != nil { // XXX hack @@ -450,12 +452,12 @@ func handlerStaticFile(logger *slog.Logger, app gotuna.App, cfg *Config) http.Ha fpath := filepath.Clean(vars["path"]) f, err := fs.Open(fpath) if os.IsNotExist(err) { - handleNotFound(app, cfg, fpath, w, r) + handleNotFound(logger, app, cfg, fpath, w, r) return } stat, err := f.Stat() if err != nil || stat.IsDir() { - handleNotFound(app, cfg, fpath, w, r) + handleNotFound(logger, app, cfg, fpath, w, r) return } @@ -473,7 +475,7 @@ func handlerFavicon(logger *slog.Logger, app gotuna.App, cfg *Config) http.Handl fpath := "img/favicon.ico" f, err := fs.Open(fpath) if os.IsNotExist(err) { - handleNotFound(app, cfg, fpath, w, r) + handleNotFound(logger, app, cfg, fpath, w, r) return } w.Header().Set("Content-Type", "image/x-icon") @@ -482,11 +484,17 @@ func handlerFavicon(logger *slog.Logger, app gotuna.App, cfg *Config) http.Handl }) } -func handleNotFound(app gotuna.App, cfg *Config, path string, w http.ResponseWriter, r *http.Request) { +func handleNotFound(logger *slog.Logger, app gotuna.App, cfg *Config, path string, w http.ResponseWriter, r *http.Request) { + // decode path for non-ascii characters + decodedPath, err := url.PathUnescape(path) + if err != nil { + logger.Error("failed to decode path", err) + decodedPath = path + } w.WriteHeader(http.StatusNotFound) app.NewTemplatingEngine(). Set("title", "Not found"). - Set("path", path). + Set("path", decodedPath). Set("Config", cfg). Render(w, r, "404.html", "funcs.html") } @@ -507,7 +515,7 @@ func pathOf(diruri string) string { parts := strings.Split(diruri, "/") if parts[0] == "gno.land" { return "/" + strings.Join(parts[1:], "/") - } else { - panic(fmt.Sprintf("invalid dir-URI %q", diruri)) } + + panic(fmt.Sprintf("invalid dir-URI %q", diruri)) } diff --git a/gno.land/pkg/gnoweb/gnoweb_test.go b/gno.land/pkg/gnoweb/gnoweb_test.go index 86a739b4a36..b266dc80a6a 100644 --- a/gno.land/pkg/gnoweb/gnoweb_test.go +++ b/gno.land/pkg/gnoweb/gnoweb_test.go @@ -42,9 +42,16 @@ func TestRoutes(t *testing.T) { {"/gor", found, "/game-of-realms"}, {"/blog", found, "/r/gnoland/blog"}, {"/404-not-found", notFound, "/404-not-found"}, + {"/아스키문자가아닌경로", notFound, "/아스키문자가아닌경로"}, + {"/%ED%85%8C%EC%8A%A4%ED%8A%B8", notFound, "/테스트"}, + {"/グノー", notFound, "/グノー"}, + {"/⚛️", notFound, "/⚛️"}, + {"/p/demo/flow/LICENSE", ok, "BSD 3-Clause"}, } - config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + rootdir := gnoenv.RootDir() + genesis := integration.LoadDefaultGenesisTXsFile(t, "tendermint_test", rootdir) + config, _ := integration.TestingNodeConfig(t, rootdir, genesis...) node, remoteAddr := integration.TestingInMemoryNode(t, log.NewTestingLogger(t), config) defer node.Stop() @@ -63,9 +70,7 @@ func TestRoutes(t *testing.T) { response := httptest.NewRecorder() app.Router.ServeHTTP(response, request) assert.Equal(t, r.status, response.Code) - assert.Contains(t, response.Body.String(), r.substring) - // println(response.Body.String()) }) } } @@ -93,7 +98,9 @@ func TestAnalytics(t *testing.T) { "/404-not-found", } - config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + rootdir := gnoenv.RootDir() + genesis := integration.LoadDefaultGenesisTXsFile(t, "tendermint_test", rootdir) + config, _ := integration.TestingNodeConfig(t, rootdir, genesis...) node, remoteAddr := integration.TestingInMemoryNode(t, log.NewTestingLogger(t), config) defer node.Stop() diff --git a/gno.land/pkg/gnoweb/static/css/app.css b/gno.land/pkg/gnoweb/static/css/app.css index 1ddd261a882..c10fc8ec0e0 100644 --- a/gno.land/pkg/gnoweb/static/css/app.css +++ b/gno.land/pkg/gnoweb/static/css/app.css @@ -48,7 +48,10 @@ html[data-theme="light"] { --quote-background: #ddd; --quote-2-background: #aaa4; --code-background: #d7d9db; - --header-background: #d7d9db; + --header-background: #373737; + --header-forground: #ffffff; + --logo-hat: #ffffff; + --logo-beard: #808080; --realm-help-background-color: #d7d9db9e; --realm-help-odd-background-color: #d7d9db45; @@ -84,7 +87,10 @@ html[data-theme="dark"] { --quote-background: #404040; --quote-2-background: #555555; --code-background: #606060; - --header-background: #606060; + --header-background: #373737; + --header-forground: #ffffff; + --logo-hat: #ffffff; + --logo-beard: #808080; --realm-help-background-color: #45454545; --realm-help-odd-background-color: #4545459e; @@ -108,14 +114,15 @@ html[data-theme="dark"] { --highlight-addition: #76c490; } -html[data-theme="dark"] #header img, -html[data-theme="dark"] .footer img { - filter: invert(1); -} +.logo-wording path {fill: var(--header-forground, #ffffff); } +.logo-beard { fill: var(--logo-beard, #808080); } +.logo-hat {fill: var(--logo-hat, #ffffff); } #theme-toggle { + cursor: pointer; display: inline-block; padding: 0; + color: var(--header-forground, #ffffff); } html[data-theme="dark"] #theme-toggle-moon, @@ -147,8 +154,8 @@ html, body { padding: 0; margin: 0; - font-family: "Roboto Mono", "Courier New", "sans-serif"; - background-color: var(--background-color, #eee); + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; background-color: var(--background-color, #eee); color: var(--text-color, #000); font-size: 15px; transition: 0.25s all ease; @@ -159,8 +166,7 @@ h2, h3, h4, nav { - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol", "Noto Color Emoji"; + font-weight: 600; letter-spacing: 0.08rem; } @@ -234,13 +240,22 @@ a[href="#"] { color: var(--muted-color, #757575); } -ul { +.gno-tmpl-section ul { padding: 0; } -li { +.gno-tmpl-section li , +#header li , +.footer li { list-style: none; - margin-bottom: 0.4rem; +} + +.gno-tmpl-section blockquote { + margin-inline: 0; +} + +li { + margin-bottom: 0.4rem; } li > * { @@ -248,7 +263,6 @@ li > * { } input { - font-family: "Roboto Mono", "Monaco", monospace; background-color: var(--input-background-color, #eee); border: 1px solid var(--border-color); color: var(--text-color, #000); @@ -266,6 +280,9 @@ blockquote blockquote { background-color: var(--quote-2-background, #aaa4); } +pre, code { + font-family: "Roboto Mono", "Courier New", "sans-serif"; +} pre { background-color: var(--code-background, #d7d9db); margin: 0; @@ -296,6 +313,11 @@ code { .container p > img:only-child { max-width: 100%; } +.gno-tmpl-page p img:only-child { + margin-inline: auto; + display: block; + max-width: 100%; +} .inline-list { padding: 1rem; @@ -338,13 +360,16 @@ code { } .footer { + text-align: center; margin-block-start: 2rem; background-color: var(--header-background, #d7d9db); border-top: 1px solid var(--border-color); } -.footer > img { - display: block; - margin: 1rem auto; + +.footer > .logo { + display: inline-block; + margin: 1rem; + height: 1.2rem; } /** 51.2rem **/ @@ -488,15 +513,19 @@ code { flex-grow: 2; } -#logo { +#header .logo { display: flex; align-items: center; color: var(--link-color, #25172a); position: absolute; - height: 3rem; + height: 2.4rem; z-index: 2; } +.logo > svg { + height: 100%; +} + #logo_path a { text-decoration: none; } @@ -610,7 +639,7 @@ code { height: 0.133rem; margin-bottom: 0.333rem; position: relative; - background: var(--text-color, #000); + background: var(--header-forground, #ffffff); z-index: 1; transform-origin: 50% 50%; /* transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), background 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), opacity 0.55s ease; */ @@ -670,6 +699,11 @@ code { display: flex; flex-direction: column; margin-bottom: 2rem; + margin-top: .8rem; +} + +#menu-toggle > .navigation a { + color: var(--header-forground, #ffffff); } #menu-toggle input:checked ~ .navigation { diff --git a/gno.land/pkg/gnoweb/static/font/README.md b/gno.land/pkg/gnoweb/static/font/README.md index 5c840f92f74..a077b97d304 100644 --- a/gno.land/pkg/gnoweb/static/font/README.md +++ b/gno.land/pkg/gnoweb/static/font/README.md @@ -1,5 +1,5 @@ # FONTS -This is the source repository for Gno.land fonts: Roboto. Both are Open source and available from Google Fonts. +This is the source repository for gno.land fonts: Roboto. Both are Open source and available from Google Fonts. - RobotoMono: Apache 2.0 — [https://fonts.google.com/specimen/Roboto+Mono/about](https://fonts.google.com/specimen/Roboto+Mono/about) diff --git a/gno.land/pkg/gnoweb/static/img/apple-touch-icon.png b/gno.land/pkg/gnoweb/static/img/apple-touch-icon.png new file mode 100644 index 00000000000..dcc70338eaa Binary files /dev/null and b/gno.land/pkg/gnoweb/static/img/apple-touch-icon.png differ diff --git a/gno.land/pkg/gnoweb/static/img/favicon-16x16.png b/gno.land/pkg/gnoweb/static/img/favicon-16x16.png new file mode 100644 index 00000000000..ee407d9a7ea Binary files /dev/null and b/gno.land/pkg/gnoweb/static/img/favicon-16x16.png differ diff --git a/gno.land/pkg/gnoweb/static/img/favicon-32x32.png b/gno.land/pkg/gnoweb/static/img/favicon-32x32.png new file mode 100644 index 00000000000..86dcfed925c Binary files /dev/null and b/gno.land/pkg/gnoweb/static/img/favicon-32x32.png differ diff --git a/gno.land/pkg/gnoweb/static/img/favicon.ico b/gno.land/pkg/gnoweb/static/img/favicon.ico index 7efef829d09..528c362c44a 100644 Binary files a/gno.land/pkg/gnoweb/static/img/favicon.ico and b/gno.land/pkg/gnoweb/static/img/favicon.ico differ diff --git a/gno.land/pkg/gnoweb/static/img/logo.png b/gno.land/pkg/gnoweb/static/img/logo.png deleted file mode 100644 index 667d07a793e..00000000000 Binary files a/gno.land/pkg/gnoweb/static/img/logo.png and /dev/null differ diff --git a/gno.land/pkg/gnoweb/static/img/logo.svg b/gno.land/pkg/gnoweb/static/img/logo.svg deleted file mode 100644 index e201882e4e1..00000000000 --- a/gno.land/pkg/gnoweb/static/img/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/gno.land/pkg/gnoweb/static/img/og-gnoland.png b/gno.land/pkg/gnoweb/static/img/og-gnoland.png new file mode 100644 index 00000000000..9872fea17da Binary files /dev/null and b/gno.land/pkg/gnoweb/static/img/og-gnoland.png differ diff --git a/gno.land/pkg/gnoweb/static/img/safari-pinned-tab.svg b/gno.land/pkg/gnoweb/static/img/safari-pinned-tab.svg new file mode 100644 index 00000000000..0005420c58d --- /dev/null +++ b/gno.land/pkg/gnoweb/static/img/safari-pinned-tab.svg @@ -0,0 +1,29 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/gno.land/pkg/gnoweb/static/js/realm_help.js b/gno.land/pkg/gnoweb/static/js/realm_help.js index 51c2675c13d..30cfacd5f59 100644 --- a/gno.land/pkg/gnoweb/static/js/realm_help.js +++ b/gno.land/pkg/gnoweb/static/js/realm_help.js @@ -29,7 +29,7 @@ function setMyAddress(addr) { function getMyAddress() { var myAddr = localStorage.getItem("my_address"); if (!myAddr) { - return "ADDRESS"; + return ""; } return myAddr; } @@ -45,13 +45,13 @@ function updateCommand(x) { ins.each(function(input) { vals.push(input.value); }); - var myAddr = getMyAddress(); + var myAddr = getMyAddress() || "ADDRESS"; var shell = x.find(".shell_command"); shell.empty(); // command Z: all in one. shell.append(u("").text("### INSECURE BUT QUICK ###")).append(u("
")); - var args = ["gnokey", "maketx", "call", + var args = ["gnokey", "maketx", "call", "-pkgpath", shq(realmPath), "-func", shq(funcName), "-gas-fee", "1000000ugnot", "-gas-wanted", "2000000", "-send", shq(""), @@ -84,20 +84,19 @@ function updateCommand(x) { }); args.push(myAddr) var command = args.join(" "); - command = command + " > unsigned.tx"; + command = command + " > call.tx"; shell.append(u("").text(command)).append(u("
")); // command 2: sign tx. var args = ["gnokey", "sign", - "-txpath", "unsigned.tx", "-chainid", shq(chainid), - "-number", "ACCOUNTNUMBER", - "-sequence", "SEQUENCENUMBER", myAddr]; + "-tx-path", "call.tx", "-chainid", shq(chainid), + "-account-number", "ACCOUNTNUMBER", + "-account-sequence", "SEQUENCENUMBER", myAddr]; var command = args.join(" "); - command = command + " > signed.tx"; shell.append(u("").text(command)).append(u("
")); // command 3: broadcast tx. - var args = ["gnokey", "broadcast", "-remote", shq(remote), "signed.tx"]; + var args = ["gnokey", "broadcast", "-remote", shq(remote), "call.tx"]; var command = args.join(" "); command = command; shell.append(u("").text(command)).append(u("
")); diff --git a/gno.land/pkg/gnoweb/views/faucet.html b/gno.land/pkg/gnoweb/views/faucet.html index 066596f7145..1c9ca1de53c 100644 --- a/gno.land/pkg/gnoweb/views/faucet.html +++ b/gno.land/pkg/gnoweb/views/faucet.html @@ -7,9 +7,9 @@
- +
- This is the Gno.land (test) {{ if .Data.Config.CaptchaSite }} + This is the gno.land (test) {{ if .Data.Config.CaptchaSite }} {{ end }} {{- end -}} -{{- define "header_logo" -}} - -{{- end -}} +{{- end -}} {{- define "footer" -}}
- Gno.land + {{ template "logo" }}
{{- end -}} diff --git a/gno.land/pkg/gnoweb/views/generic.html b/gno.land/pkg/gnoweb/views/generic.html index e671625e26a..5bcd14c3a46 100644 --- a/gno.land/pkg/gnoweb/views/generic.html +++ b/gno.land/pkg/gnoweb/views/generic.html @@ -7,7 +7,7 @@
- +
           {{- .Data.MainContent -}}
diff --git a/gno.land/pkg/gnoweb/views/package_dir.html b/gno.land/pkg/gnoweb/views/package_dir.html
index efaf4d7ad0c..793ebd40b84 100644
--- a/gno.land/pkg/gnoweb/views/package_dir.html
+++ b/gno.land/pkg/gnoweb/views/package_dir.html
@@ -7,7 +7,7 @@
   
   
     
- + diff --git a/gno.land/pkg/gnoweb/views/package_file.html b/gno.land/pkg/gnoweb/views/package_file.html index 71aa8b68452..42e1d0a28fc 100644 --- a/gno.land/pkg/gnoweb/views/package_file.html +++ b/gno.land/pkg/gnoweb/views/package_file.html @@ -7,7 +7,7 @@
- +
{{ .Data.DirPath }}/{{ .Data.FileName }}
diff --git a/gno.land/pkg/gnoweb/views/realm_help.html b/gno.land/pkg/gnoweb/views/realm_help.html index 85d5571cdaf..b9c8e119e7a 100644 --- a/gno.land/pkg/gnoweb/views/realm_help.html +++ b/gno.land/pkg/gnoweb/views/realm_help.html @@ -8,7 +8,7 @@
- + @@ -17,7 +17,7 @@
These are the realm's exposed functions ("public smart contracts").

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


{{ template "func_specs" . }} diff --git a/gno.land/pkg/gnoweb/views/realm_render.html b/gno.land/pkg/gnoweb/views/realm_render.html index 6337d77aafa..9a4507777a6 100644 --- a/gno.land/pkg/gnoweb/views/realm_render.html +++ b/gno.land/pkg/gnoweb/views/realm_render.html @@ -7,7 +7,7 @@
- +
/r/{{ .Data.RealmName }} diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index bea0fe78349..2b6d24c23b8 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -45,6 +45,12 @@ // - It's important to note that the load order is significant when using multiple `loadpkg` // command; packages should be loaded in the order they are dependent upon. // +// 6. `patchpkg`: +// - Patches any loaded files by package by replacing all occurrences of the first argument with the second. +// - This is mostly used to replace hardcoded addresses from loaded packages. +// - NOTE: this command may only be temporary, as it's not best approach to +// solve the above problem +// // Logging: // // Gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much diff --git a/gno.land/pkg/integration/testdata/adduser.txtar b/gno.land/pkg/integration/testdata/adduser.txtar index ebd0e4abb43..8c5706a68c4 100644 --- a/gno.land/pkg/integration/testdata/adduser.txtar +++ b/gno.land/pkg/integration/testdata/adduser.txtar @@ -14,6 +14,7 @@ stdout 'main: --- hello from foo ---' stdout 'OK!' stdout 'GAS WANTED: 200000' stdout 'GAS USED: ' +stdout 'TX HASH: ' # should fail if user is added after node is started ! adduser test5 diff --git a/gno.land/pkg/integration/testdata/improved-coins.txtar b/gno.land/pkg/integration/testdata/improved-coins.txtar new file mode 100644 index 00000000000..8950371afb3 --- /dev/null +++ b/gno.land/pkg/integration/testdata/improved-coins.txtar @@ -0,0 +1,86 @@ +loadpkg gno.land/r/demo/coins $WORK + +gnoland start + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "MakeNewCoins" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(300 int64)' +stdout '(321 int64)' +stdout '("ugnot" string)' +stdout '("example" string)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "AddCoin" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(300 int64)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "SubCoin" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(123 int64)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "StringZeroCoin" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '("0ugnot" string)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "IsZero" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(true bool)' +stdout '(false bool)' +stdout '(false bool)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "IsPositive" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(false bool)' +stdout '(false bool)' +stdout '(true bool)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "IsNegative" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(true bool)' +stdout '(false bool)' +stdout '(false bool)' + +-- coins.gno -- +package coins + +import "std" + +func MakeNewCoins() std.Coins { + coin1 := std.NewCoin("ugnot", 123) + coin2 := std.NewCoin("example", 321) + coin3 := std.NewCoin("ugnot", 177) + + + return std.NewCoins(coin1, coin2, coin3) +} + +func AddCoin() std.Coin { + coin1 := std.NewCoin("ugnot", 123) + coin2 := std.NewCoin("ugnot", 177) + return coin1.Add(coin2) +} + +func SubCoin() std.Coin { + coin1 := std.NewCoin("ugnot", 300) + coin2 := std.NewCoin("ugnot", 177) + return coin1.Sub(coin2) +} + +func StringZeroCoin() string { + coin1 := std.NewCoin("ugnot", 0) + return coin1.String() +} + +func IsZero() (bool, bool, bool) { + coin1 := std.NewCoin("ugnot", 0) + coin2 := std.NewCoin("ugnot", 123) + coin3 := std.NewCoin("ugnot", -123) + return coin1.IsZero(), coin2.IsZero(), coin3.IsZero() +} + +func IsPositive() (bool, bool, bool) { + coin1 := std.NewCoin("ugnot", -123) + coin2 := std.NewCoin("ugnot", 0) + coin3 := std.NewCoin("ugnot", 123) + return coin1.IsPositive(), coin2.IsPositive(), coin3.IsPositive() +} + +func IsNegative() (bool, bool, bool) { + coin1 := std.NewCoin("ugnot", -123) + coin2 := std.NewCoin("ugnot", 0) + coin3 := std.NewCoin("ugnot", 123) + return coin1.IsNegative(), coin2.IsNegative(), coin3.IsNegative() +} + diff --git a/gno.land/pkg/integration/testdata/loadpkg_work.txtar b/gno.land/pkg/integration/testdata/loadpkg_work.txtar index 5fb9a07c5de..e789c171dc2 100644 --- a/gno.land/pkg/integration/testdata/loadpkg_work.txtar +++ b/gno.land/pkg/integration/testdata/loadpkg_work.txtar @@ -12,6 +12,7 @@ stdout 'main: --- hello from foo ---' stdout 'OK!' stdout 'GAS WANTED: 200000' stdout 'GAS USED: ' +stdout 'TX HASH: ' -- bar/bar.gno -- package bar diff --git a/gno.land/pkg/integration/testdata/patchpkg.txtar b/gno.land/pkg/integration/testdata/patchpkg.txtar new file mode 100644 index 00000000000..c5962709625 --- /dev/null +++ b/gno.land/pkg/integration/testdata/patchpkg.txtar @@ -0,0 +1,20 @@ +loadpkg gno.land/r/dev/admin $WORK + +adduser dev + +patchpkg "g1abcde" $USER_ADDR_dev + +gnoland start + +gnokey maketx call -pkgpath gno.land/r/dev/admin -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +! stdout g1abcde +stdout $USER_ADDR_dev + +-- admin.gno -- +package admin + +var admin = "g1abcde" + +func Render(path string) string { + return "# Hello "+admin +} diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 654dda0b45e..0462b0c7639 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -15,6 +15,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/keyscli" "github.com/gnolang/gno/gno.land/pkg/log" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -259,7 +260,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { err = cmd.ParseAndRun(context.Background(), args) tsValidateError(ts, "gnokey", neg, err) }, - // adduser commands must be executed before starting the node; it errors out otherwise. + // adduser command must be executed before starting the node; it errors out otherwise. "adduser": func(ts *testscript.TestScript, neg bool, args []string) { if nodeIsRunning(nodes, getNodeSID(ts)) { tsValidateError(ts, "adduser", neg, errors.New("adduser must be used before starting node")) @@ -339,7 +340,24 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { fmt.Fprintf(ts.Stdout(), "Added %s(%s) to genesis", args[0], balance.Address) }, - // `loadpkg` load a specific package from the 'examples' or working directory + // `patchpkg` Patch any loaded files by packages by replacing all occurrences of the + // first argument with the second. + // This is mostly use to replace hardcoded address inside txtar file. + "patchpkg": func(ts *testscript.TestScript, neg bool, args []string) { + args, err := unquote(args) + if err != nil { + tsValidateError(ts, "patchpkg", neg, err) + } + + if len(args) != 2 { + ts.Fatalf("`patchpkg`: should have exactly 2 arguments") + } + + pkgs := ts.Value(envKeyPkgsLoader).(*pkgsLoader) + replace, with := args[0], args[1] + pkgs.SetPatch(replace, with) + }, + // `loadpkg` load a specific package from the 'examples' or working directory. "loadpkg": func(ts *testscript.TestScript, neg bool, args []string) { // special dirs workDir := ts.Getenv("WORK") @@ -575,12 +593,17 @@ func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic str type pkgsLoader struct { pkgs []gnomod.Pkg visited map[string]struct{} + + // list of occurrences to patchs with the given value + // XXX: find a better way + patchs map[string]string } func newPkgsLoader() *pkgsLoader { return &pkgsLoader{ pkgs: make([]gnomod.Pkg, 0), visited: make(map[string]struct{}), + patchs: make(map[string]string), } } @@ -588,6 +611,10 @@ func (pl *pkgsLoader) List() gnomod.PkgList { return pl.pkgs } +func (pl *pkgsLoader) SetPatch(replace, with string) { + pl.patchs[replace] = with +} + func (pl *pkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { pkgslist, err := pl.List().Sort() // sorts packages by their dependencies. if err != nil { @@ -600,6 +627,27 @@ func (pl *pkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std if err != nil { return nil, fmt.Errorf("unable to load pkg %q: %w", pkg.Name, err) } + + // If any replace value is specified, apply them + if len(pl.patchs) > 0 { + for _, msg := range tx.Msgs { + addpkg, ok := msg.(vm.MsgAddPackage) + if !ok { + continue + } + + if addpkg.Package == nil { + continue + } + + for _, file := range addpkg.Package.Files { + for replace, with := range pl.patchs { + file.Body = strings.ReplaceAll(file.Body, replace, with) + } + } + } + } + txs[i] = tx } diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go index 66fc50e7953..993386f6b04 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -42,7 +42,7 @@ func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMem // TestingNodeConfig constructs an in-memory node configuration // with default packages and genesis transactions already loaded. // It will return the default creator address of the loaded packages. -func TestingNodeConfig(t TestingTS, gnoroot string) (*gnoland.InMemoryNodeConfig, bft.Address) { +func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...std.Tx) (*gnoland.InMemoryNodeConfig, bft.Address) { cfg := TestingMinimalNodeConfig(t, gnoroot) creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 @@ -50,7 +50,7 @@ func TestingNodeConfig(t TestingTS, gnoroot string) (*gnoland.InMemoryNodeConfig balances := LoadDefaultGenesisBalanceFile(t, gnoroot) txs := []std.Tx{} txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...) - txs = append(txs, LoadDefaultGenesisTXsFile(t, cfg.Genesis.ChainID, gnoroot)...) + txs = append(txs, additionalTxs...) cfg.Genesis.AppState = gnoland.GnoGenesisState{ Balances: balances, @@ -115,7 +115,7 @@ func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std examplesDir := filepath.Join(gnoroot, "examples") defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee, nil) + txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee) require.NoError(t, err) return txs diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 9cac68c4c79..980d507c430 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -9,6 +9,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/errors" @@ -107,12 +108,36 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { if err != nil { panic(err) } - // construct msg & tx and marshal. + msg := vm.MsgAddPackage{ Creator: creator, Package: memPkg, Deposit: deposit, } + + // if a sponsor onchain address is specified + if cfg.RootCfg.Sponsor != "" { + sponsorAddress, err := crypto.AddressFromBech32(cfg.RootCfg.Sponsor) + if err != nil { + return errors.Wrap(err, "invalid sponsor address") + } + + tx := std.Tx{ + Msgs: []std.Msg{vm.NewMsgNoop(sponsorAddress), msg}, + Fee: std.NewFee(gaswanted, gasfee), + Signatures: nil, + Memo: cfg.RootCfg.Memo, + } + + if cfg.RootCfg.Broadcast { + return client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) + } + + io.Println(string(amino.MustMarshalJSON(tx))) + + return nil + } + tx := std.Tx{ Msgs: []std.Msg{msg}, Fee: std.NewFee(gaswanted, gasfee), @@ -121,12 +146,10 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { } if cfg.RootCfg.Broadcast { - err := client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) - if err != nil { - return err - } - } else { - fmt.Println(string(amino.MustMarshalJSON(tx))) + return client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) } + + io.Println(string(amino.MustMarshalJSON(tx))) + return nil } diff --git a/gno.land/pkg/keyscli/call.go b/gno.land/pkg/keyscli/call.go index 7eb5be7d501..b30e5c93717 100644 --- a/gno.land/pkg/keyscli/call.go +++ b/gno.land/pkg/keyscli/call.go @@ -3,11 +3,11 @@ package keyscli import ( "context" "flag" - "fmt" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/errors" @@ -116,7 +116,6 @@ func execMakeCall(cfg *MakeCallCfg, args []string, io commands.IO) error { return errors.Wrap(err, "parsing gas fee coin") } - // construct msg & tx and marshal. msg := vm.MsgCall{ Caller: caller, Send: send, @@ -124,6 +123,30 @@ func execMakeCall(cfg *MakeCallCfg, args []string, io commands.IO) error { Func: fnc, Args: cfg.Args, } + + // if a sponsor onchain address is specified + if cfg.RootCfg.Sponsor != "" { + sponsorAddress, err := crypto.AddressFromBech32(cfg.RootCfg.Sponsor) + if err != nil { + return errors.Wrap(err, "invalid sponsor address") + } + + tx := std.Tx{ + Msgs: []std.Msg{vm.NewMsgNoop(sponsorAddress), msg}, + Fee: std.NewFee(gaswanted, gasfee), + Signatures: nil, + Memo: cfg.RootCfg.Memo, + } + + if cfg.RootCfg.Broadcast { + return client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) + } + + io.Println(string(amino.MustMarshalJSON(tx))) + + return nil + } + tx := std.Tx{ Msgs: []std.Msg{msg}, Fee: std.NewFee(gaswanted, gasfee), @@ -132,12 +155,10 @@ func execMakeCall(cfg *MakeCallCfg, args []string, io commands.IO) error { } if cfg.RootCfg.Broadcast { - err := client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) - if err != nil { - return err - } - } else { - fmt.Println(string(amino.MustMarshalJSON(tx))) + return client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) } + + io.Println(string(amino.MustMarshalJSON(tx))) + return nil } diff --git a/gno.land/pkg/keyscli/maketx.go b/gno.land/pkg/keyscli/maketx.go index 117a9bb3c3d..b4b338dd46d 100644 --- a/gno.land/pkg/keyscli/maketx.go +++ b/gno.land/pkg/keyscli/maketx.go @@ -16,6 +16,9 @@ type MakeTxCfg struct { Broadcast bool ChainID string + + // Optional + Sponsoree string } func NewMakeTxCmd(rootCfg *client.BaseCfg, io commands.IO) *commands.Command { @@ -80,4 +83,11 @@ func (c *MakeTxCfg) RegisterFlags(fs *flag.FlagSet) { "dev", "chainid to sign for (only useful if --broadcast)", ) + + fs.StringVar( + &c.Sponsoree, + "sponsoree", + "", + "address of sponsoree", + ) } diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 9ae6132c8f3..2eb0543b984 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -11,6 +11,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/errors" @@ -113,11 +114,34 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { // Set to empty; this will be automatically set by the VM keeper. memPkg.Path = "" - // construct msg & tx and marshal. msg := vm.MsgRun{ Caller: caller, Package: memPkg, } + + // if a sponsor onchain address is specified + if cfg.RootCfg.Sponsor != "" { + sponsorAddress, err := crypto.AddressFromBech32(cfg.RootCfg.Sponsor) + if err != nil { + return errors.Wrap(err, "invalid sponsor address") + } + + tx := std.Tx{ + Msgs: []std.Msg{vm.NewMsgNoop(sponsorAddress), msg}, + Fee: std.NewFee(gaswanted, gasfee), + Signatures: nil, + Memo: cfg.RootCfg.Memo, + } + + if cfg.RootCfg.Broadcast { + return client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, cmdio) + } + + cmdio.Println(string(amino.MustMarshalJSON(tx))) + + return nil + } + tx := std.Tx{ Msgs: []std.Msg{msg}, Fee: std.NewFee(gaswanted, gasfee), @@ -126,12 +150,10 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { } if cfg.RootCfg.Broadcast { - err := client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, cmdio) - if err != nil { - return err - } - } else { - fmt.Println(string(amino.MustMarshalJSON(tx))) + return client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, cmdio) } + + cmdio.Println(string(amino.MustMarshalJSON(tx))) + return nil } diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 368ada6ff82..de58cd3e8ae 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -1,48 +1,11 @@ package vm import ( - "os" - "path/filepath" - - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/crypto" - osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" ) -func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { - // NOTE: native functions/methods added here must be quick operations, - // or account for gas before operation. - // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { - // otherwise, built-in package value. - // first, load from filepath. - stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) - if !osm.DirExists(stdlibPath) { - // does not exist. - return nil, nil - } - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - if memPkg.IsEmpty() { - // no gno files are present, skip this package - return nil, nil - } - - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "gno.land/r/stdlibs/" + pkgPath, - // PkgPath: pkgPath, - Output: os.Stdout, - Store: newStore, - }) - defer m2.Release() - return m2.RunMemPackage(memPkg, true) - } - store.SetPackageGetter(getPackage) - store.SetNativeStore(stdlibs.NativeStore) -} - // ---------------------------------------- // SDKBanker diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 216de980aff..6dd8050d6b6 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -25,6 +25,14 @@ type testEnv struct { } func setupTestEnv() testEnv { + return _setupTestEnv(true) +} + +func setupTestEnvCold() testEnv { + return _setupTestEnv(false) +} + +func _setupTestEnv(cacheStdlibs bool) testEnv { db := memdb.NewMemDB() baseCapKey := store.NewStoreKey("baseCapKey") @@ -41,7 +49,9 @@ func setupTestEnv() testEnv { stdlibsDir := filepath.Join("..", "..", "..", "..", "gnovm", "stdlibs") vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir, 100_000_000) - vmk.Initialize(ms.MultiCacheWrap()) + mcw := ms.MultiCacheWrap() + vmk.Initialize(log.NewNoopLogger(), mcw, cacheStdlibs) + mcw.MultiWrite() return testEnv{ctx: ctx, vmk: vmk, bank: bank, acck: acck} } diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index f70f99403a8..cafb6cad67f 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -4,13 +4,14 @@ import ( "encoding/base64" "fmt" "strconv" + "strings" "github.com/cockroachdb/apd/v3" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" ) -func assertCharNotPlus(b byte) { - if b == '+' { +func assertNoPlusPrefix(s string) { + if strings.HasPrefix(s, "+") { panic("numbers cannot start with +") } } @@ -41,7 +42,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetString(gno.StringValue(arg)) return case gno.IntType: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) i64, err := strconv.ParseInt(arg, 10, 64) if err != nil { panic(fmt.Sprintf( @@ -51,7 +52,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetInt(int(i64)) return case gno.Int8Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) i8, err := strconv.ParseInt(arg, 10, 8) if err != nil { panic(fmt.Sprintf( @@ -61,7 +62,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetInt8(int8(i8)) return case gno.Int16Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) i16, err := strconv.ParseInt(arg, 10, 16) if err != nil { panic(fmt.Sprintf( @@ -71,7 +72,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetInt16(int16(i16)) return case gno.Int32Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) i32, err := strconv.ParseInt(arg, 10, 32) if err != nil { panic(fmt.Sprintf( @@ -81,7 +82,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetInt32(int32(i32)) return case gno.Int64Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) i64, err := strconv.ParseInt(arg, 10, 64) if err != nil { panic(fmt.Sprintf( @@ -91,7 +92,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetInt64(i64) return case gno.UintType: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) u64, err := strconv.ParseUint(arg, 10, 64) if err != nil { panic(fmt.Sprintf( @@ -101,7 +102,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetUint(uint(u64)) return case gno.Uint8Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) u8, err := strconv.ParseUint(arg, 10, 8) if err != nil { panic(fmt.Sprintf( @@ -111,7 +112,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetUint8(uint8(u8)) return case gno.Uint16Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) u16, err := strconv.ParseUint(arg, 10, 16) if err != nil { panic(fmt.Sprintf( @@ -121,7 +122,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetUint16(uint16(u16)) return case gno.Uint32Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) u32, err := strconv.ParseUint(arg, 10, 32) if err != nil { panic(fmt.Sprintf( @@ -131,7 +132,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { tv.SetUint32(uint32(u32)) return case gno.Uint64Type: - assertCharNotPlus(arg[0]) + assertNoPlusPrefix(arg) u64, err := strconv.ParseUint(arg, 10, 64) if err != nil { panic(fmt.Sprintf( @@ -192,15 +193,15 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { } func convertFloat(value string, precision int) float64 { - assertCharNotPlus(value[0]) + assertNoPlusPrefix(value) dec, _, err := apd.NewFromString(value) if err != nil { - panic(fmt.Sprintf("error parsing float%d %s: %v", precision, value, err)) + panic(fmt.Sprintf("error parsing float%d %q: %v", precision, value, err)) } f64, err := strconv.ParseFloat(dec.String(), precision) if err != nil { - panic(fmt.Sprintf("error value exceeds float%d precision %s: %v", precision, value, err)) + panic(fmt.Sprintf("error value exceeds float%d precision %q: %v", precision, value, err)) } return f64 diff --git a/gno.land/pkg/sdk/vm/convert_test.go b/gno.land/pkg/sdk/vm/convert_test.go new file mode 100644 index 00000000000..666ec1620fa --- /dev/null +++ b/gno.land/pkg/sdk/vm/convert_test.go @@ -0,0 +1,39 @@ +package vm + +import ( + "fmt" + "testing" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/stretchr/testify/assert" +) + +func TestConvertEmptyNumbers(t *testing.T) { + tests := []struct { + argT gnolang.Type + expectedErr string + }{ + {gnolang.UintType, `error parsing uint "": strconv.ParseUint: parsing "": invalid syntax`}, + {gnolang.Uint64Type, `error parsing uint64 "": strconv.ParseUint: parsing "": invalid syntax`}, + {gnolang.Uint32Type, `error parsing uint32 "": strconv.ParseUint: parsing "": invalid syntax`}, + {gnolang.Uint16Type, `error parsing uint16 "": strconv.ParseUint: parsing "": invalid syntax`}, + {gnolang.Uint8Type, `error parsing uint8 "": strconv.ParseUint: parsing "": invalid syntax`}, + {gnolang.IntType, `error parsing int "": strconv.ParseInt: parsing "": invalid syntax`}, + {gnolang.Int64Type, `error parsing int64 "": strconv.ParseInt: parsing "": invalid syntax`}, + {gnolang.Int32Type, `error parsing int32 "": strconv.ParseInt: parsing "": invalid syntax`}, + {gnolang.Int16Type, `error parsing int16 "": strconv.ParseInt: parsing "": invalid syntax`}, + {gnolang.Int8Type, `error parsing int8 "": strconv.ParseInt: parsing "": invalid syntax`}, + {gnolang.Float64Type, `error parsing float64 "": parse mantissa: `}, + {gnolang.Float32Type, `error parsing float32 "": parse mantissa: `}, + } + + for _, tt := range tests { + testname := fmt.Sprintf("%v", tt.argT) + t.Run(testname, func(t *testing.T) { + run := func() { + _ = convertArgToGno("", tt.argT) + } + assert.PanicsWithValue(t, tt.expectedErr, run) + }) + } +} diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go index 132d98b7ecd..a0e71e08d14 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -15,18 +15,20 @@ func (abciError) AssertABCIError() {} // declare all script errors. // NOTE: these are meant to be used in conjunction with pkgs/errors. type ( - InvalidPkgPathError struct{ abciError } - InvalidStmtError struct{ abciError } - InvalidExprError struct{ abciError } - TypeCheckError struct { + InvalidPkgPathError struct{ abciError } + InvalidStmtError struct{ abciError } + InvalidExprError struct{ abciError } + UnauthorizedUserError struct{ abciError } + TypeCheckError struct { abciError - Errors []string + Errors []string `json:"errors"` } ) -func (e InvalidPkgPathError) Error() string { return "invalid package path" } -func (e InvalidStmtError) Error() string { return "invalid statement" } -func (e InvalidExprError) Error() string { return "invalid expression" } +func (e InvalidPkgPathError) Error() string { return "invalid package path" } +func (e InvalidStmtError) Error() string { return "invalid statement" } +func (e InvalidExprError) Error() string { return "invalid expression" } +func (e UnauthorizedUserError) Error() string { return "unauthorized user" } func (e TypeCheckError) Error() string { var bld strings.Builder bld.WriteString("invalid gno package; type check errors:\n") @@ -34,6 +36,10 @@ func (e TypeCheckError) Error() string { return bld.String() } +func ErrUnauthorizedUser(msg string) error { + return errors.Wrap(UnauthorizedUserError{}, msg) +} + func ErrInvalidPkgPath(msg string) error { return errors.Wrap(InvalidPkgPathError{}, msg) } diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 9f4ae1a6678..66655994bd4 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -67,7 +67,9 @@ func TestAddPkgDeliverTx(t *testing.T) { gasDeliver := gctx.GasMeter().GasConsumed() assert.True(t, res.IsOK()) - assert.Equal(t, int64(87809), gasDeliver) + + // NOTE: let's try to keep this bellow 100_000 :) + assert.Equal(t, int64(92825), gasDeliver) } // Enough gas for a failed transaction. diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go index e1dd31846e7..a19ed5f5c65 100644 --- a/gno.land/pkg/sdk/vm/handler.go +++ b/gno.land/pkg/sdk/vm/handler.go @@ -1,12 +1,17 @@ package vm import ( + "context" "fmt" "strings" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/telemetry" + "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" ) type vmHandler struct { @@ -28,6 +33,8 @@ func (vh vmHandler) Process(ctx sdk.Context, msg std.Msg) sdk.Result { return vh.handleMsgCall(ctx, msg) case MsgRun: return vh.handleMsgRun(ctx, msg) + case MsgNoop: + return sdk.Result{} default: errMsg := fmt.Sprintf("unrecognized vm message type: %T", msg) return abciResult(std.ErrUnknownRequest(errMsg)) @@ -51,14 +58,6 @@ func (vh vmHandler) handleMsgCall(ctx sdk.Context, msg MsgCall) (res sdk.Result) } res.Data = []byte(resstr) return - /* TODO handle events. - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyXXX, types.AttributeValueXXX), - ), - ) - */ } // Handle MsgRun. @@ -71,7 +70,7 @@ func (vh vmHandler) handleMsgRun(ctx sdk.Context, msg MsgRun) (res sdk.Result) { return } -//---------------------------------------- +// ---------------------------------------- // Query // query paths @@ -84,27 +83,58 @@ const ( QueryFile = "qfile" ) -func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - switch secondPart(req.Path) { +func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQuery { + var ( + res abci.ResponseQuery + path = secondPart(req.Path) + ) + + switch path { case QueryPackage: - return vh.queryPackage(ctx, req) + res = vh.queryPackage(ctx, req) case QueryStore: - return vh.queryStore(ctx, req) + res = vh.queryStore(ctx, req) case QueryRender: - return vh.queryRender(ctx, req) + res = vh.queryRender(ctx, req) case QueryFuncs: - return vh.queryFuncs(ctx, req) + res = vh.queryFuncs(ctx, req) case QueryEval: - return vh.queryEval(ctx, req) + res = vh.queryEval(ctx, req) case QueryFile: - return vh.queryFile(ctx, req) + res = vh.queryFile(ctx, req) default: - res = sdk.ABCIResponseQueryFromError( + return sdk.ABCIResponseQueryFromError( std.ErrUnknownRequest(fmt.Sprintf( "unknown vm query endpoint %s in %s", secondPart(req.Path), req.Path))) + } + + // Log the telemetry + logQueryTelemetry(path, res.IsErr()) + + return res +} + +// logQueryTelemetry logs the relevant VM query telemetry +func logQueryTelemetry(path string, isErr bool) { + if !telemetry.MetricsEnabled() { return } + + metrics.VMQueryCalls.Add( + context.Background(), + 1, + metric.WithAttributes( + attribute.KeyValue{ + Key: "path", + Value: attribute.StringValue(path), + }, + ), + ) + + if isErr { + metrics.VMQueryErrors.Add(context.Background(), 1) + } } // queryPackage fetch a package's files. @@ -122,12 +152,12 @@ func (vh vmHandler) queryStore(ctx sdk.Context, req abci.RequestQuery) (res abci // queryRender calls .Render() in readonly mode. func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { reqData := string(req.Data) - reqParts := strings.Split(reqData, "\n") - if len(reqParts) != 2 { - panic("expected two lines in query input data") + dot := strings.IndexByte(reqData, ':') + if dot < 0 { + panic("expected : syntax in query input data") } - pkgPath := reqParts[0] - path := reqParts[1] + + pkgPath, path := reqData[:dot], reqData[dot+1:] expr := fmt.Sprintf("Render(%q)", path) result, err := vh.vm.QueryEvalString(ctx, pkgPath, expr) if err != nil { @@ -140,12 +170,7 @@ func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abc // queryFuncs returns public facing function signatures as JSON. func (vh vmHandler) queryFuncs(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - reqData := string(req.Data) - reqParts := strings.Split(reqData, "\n") - if len(reqParts) != 1 { - panic("expected one line in query input data") - } - pkgPath := reqParts[0] + pkgPath := string(req.Data) fsigs, err := vh.vm.QueryFuncs(ctx, pkgPath) if err != nil { res = sdk.ABCIResponseQueryFromError(err) @@ -157,13 +182,7 @@ func (vh vmHandler) queryFuncs(ctx sdk.Context, req abci.RequestQuery) (res abci // queryEval evaluates any expression in readonly mode and returns the results. func (vh vmHandler) queryEval(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - reqData := string(req.Data) - reqParts := strings.Split(reqData, "\n") - if len(reqParts) != 2 { - panic("expected two lines in query input data") - } - pkgPath := reqParts[0] - expr := reqParts[1] + pkgPath, expr := parseQueryEvalData(string(req.Data)) result, err := vh.vm.QueryEval(ctx, pkgPath, expr) if err != nil { res = sdk.ABCIResponseQueryFromError(err) @@ -173,6 +192,29 @@ func (vh vmHandler) queryEval(ctx sdk.Context, req abci.RequestQuery) (res abci. return } +// parseQueryEval parses the input string of vm/qeval. It takes the first dot +// after the first slash (if any) to separe the pkgPath and the expr. +// For instance, in gno.land/r/realm.MyFunction(), gno.land/r/realm is the +// pkgPath,and MyFunction() is the expr. +func parseQueryEvalData(data string) (pkgPath, expr string) { + slash := strings.IndexByte(data, '/') + if slash >= 0 { + pkgPath += data[:slash] + data = data[slash:] + } + dot := strings.IndexByte(data, '.') + if dot < 0 { + panic(panicInvalidQueryEvalData) + } + pkgPath += data[:dot] + expr = data[dot+1:] + return +} + +const ( + panicInvalidQueryEvalData = "expected . syntax in query input data" +) + // queryFile returns the file bytes, or list of files if directory. // if file, res.Value is []byte("file"). // if dir, res.Value is []byte("dir"). @@ -187,7 +229,7 @@ func (vh vmHandler) queryFile(ctx sdk.Context, req abci.RequestQuery) (res abci. return } -//---------------------------------------- +// ---------------------------------------- // misc func abciResult(err error) sdk.Result { diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 2110681d99d..38ac8fa61b9 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -1,58 +1,50 @@ package vm -/* - import ( - "fmt" - "strings" "testing" - "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/tm2/pkg/amino" - 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/sdk" - tu "github.com/gnolang/gno/tm2/pkg/sdk/testutils" - "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" ) -func TestInvalidMsg(t *testing.T) { - h := NewHandler(BankKeeper{}) - res := h.Process(sdk.NewContext(nil, &bft.Header{}, false, nil), tu.NewTestMsg()) - require.False(t, res.IsOK()) - require.True(t, strings.Contains(res.Log, "unrecognized bank message type")) -} - -func TestBalances(t *testing.T) { - env := setupTestEnv() - h := NewHandler(env.bank) - req := abci.RequestQuery{ - Path: fmt.Sprintf("bank/%s", QueryBalance), - Data: []byte{}, +func Test_parseQueryEvalData(t *testing.T) { + t.Parallel() + tt := []struct { + input string + pkgpath string + expr string + }{ + { + "gno.land/r/realm.Expression()", + "gno.land/r/realm", + "Expression()", + }, + { + "a.b/c/d.e", + "a.b/c/d", + "e", + }, + { + "a.b.c.d.e/c/d.e", + "a.b.c.d.e/c/d", + "e", + }, + { + "abcde/c/d.e", + "abcde/c/d", + "e", + }, } + for _, tc := range tt { + path, expr := parseQueryEvalData(tc.input) + assert.Equal(t, tc.pkgpath, path) + assert.Equal(t, tc.expr, expr) + } +} - res := h.Query(env.ctx, req) - require.NotNil(t, res.Error) - - _, _, addr := tu.KeyTestPubAddr() - req.Data = amino.MustMarshalJSON(NewQueryBalanceParams(addr)) - res = h.Query(env.ctx, req) - require.Nil(t, res.Error) // the account does not exist, no error returned anyway - require.NotNil(t, res) - - var coins std.Coins - require.NoError(t, amino.UnmarshalJSON(res.Data, &coins)) - require.True(t, coins.IsZero()) +func Test_parseQueryEval_panic(t *testing.T) { + t.Parallel() - acc := env.acck.NewAccountWithAddress(env.ctx, addr) - acc.SetCoins(std.NewCoins(std.NewCoin("foo", 10))) - env.acck.SetAccount(env.ctx, acc) - res = h.Query(env.ctx, req) - require.Nil(t, res.Error) - require.NotNil(t, res) - require.NoError(t, amino.UnmarshalJSON(res.Data, &coins)) - require.True(t, coins.AmountOf("foo") == 10) + assert.PanicsWithValue(t, panicInvalidQueryEvalData, func() { + parseQueryEvalData("gno.land/r/demo/users") + }) } - -*/ diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index ef260bd3c42..934c4557dd0 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -4,19 +4,33 @@ package vm import ( "bytes" + "context" "fmt" + "log/slog" "os" + "path/filepath" "regexp" "strings" + "sync" + "time" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/errors" + osm "github.com/gnolang/gno/tm2/pkg/os" "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/std" "github.com/gnolang/gno/tm2/pkg/store" + "github.com/gnolang/gno/tm2/pkg/store/dbadapter" + "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/telemetry" + "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" ) const ( @@ -29,6 +43,7 @@ const ( type VMKeeperI interface { AddPackage(ctx sdk.Context, msg MsgAddPackage) error Call(ctx sdk.Context, msg MsgCall) (res string, err error) + QueryEval(ctx sdk.Context, pkgPath string, expr string) (res string, err error) Run(ctx sdk.Context, msg MsgRun) (res string, err error) } @@ -69,30 +84,163 @@ func NewVMKeeper( return vmk } -func (vm *VMKeeper) Initialize(ms store.MultiStore) { +func (vm *VMKeeper) Initialize( + logger *slog.Logger, + ms store.MultiStore, + cacheStdlibLoad bool, +) { if vm.gnoStore != nil { panic("should not happen") } - alloc := gno.NewAllocator(maxAllocTx) baseSDKStore := ms.GetStore(vm.baseKey) iavlSDKStore := ms.GetStore(vm.iavlKey) - vm.gnoStore = gno.NewStore(alloc, baseSDKStore, iavlSDKStore) - vm.initBuiltinPackagesAndTypes(vm.gnoStore) - if vm.gnoStore.NumMemPackages() > 0 { + + if cacheStdlibLoad { + // Testing case (using the cache speeds up starting many nodes) + vm.gnoStore = cachedStdlibLoad(vm.stdlibsDir, baseSDKStore, iavlSDKStore) + } else { + // On-chain case + vm.gnoStore = uncachedPackageLoad(logger, vm.stdlibsDir, baseSDKStore, iavlSDKStore) + } +} + +func uncachedPackageLoad( + logger *slog.Logger, + stdlibsDir string, + baseStore, iavlStore store.Store, +) gno.Store { + alloc := gno.NewAllocator(maxAllocTx) + gnoStore := gno.NewStore(alloc, baseStore, iavlStore) + gnoStore.SetNativeStore(stdlibs.NativeStore) + if gnoStore.NumMemPackages() == 0 { + // No packages in the store; set up the stdlibs. + start := time.Now() + + loadStdlib(stdlibsDir, gnoStore) + + // XXX Quick and dirty to make this function work on non-validator nodes + iter := iavlStore.Iterator(nil, nil) + for ; iter.Valid(); iter.Next() { + baseStore.Set(append(iavlBackupPrefix, iter.Key()...), iter.Value()) + } + iter.Close() + + logger.Debug("Standard libraries initialized", + "elapsed", time.Since(start)) + } else { // for now, all mem packages must be re-run after reboot. // TODO remove this, and generally solve for in-mem garbage collection // and memory management across many objects/types/nodes/packages. + start := time.Now() + + // XXX Quick and dirty to make this function work on non-validator nodes + if isStoreEmpty(iavlStore) { + iter := baseStore.Iterator(iavlBackupPrefix, nil) + for ; iter.Valid(); iter.Next() { + if !bytes.HasPrefix(iter.Key(), iavlBackupPrefix) { + break + } + iavlStore.Set(iter.Key()[len(iavlBackupPrefix):], iter.Value()) + } + iter.Close() + } + m2 := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", Output: os.Stdout, // XXX - Store: vm.gnoStore, + Store: gnoStore, }) defer m2.Release() gno.DisableDebug() m2.PreprocessAllFilesAndSaveBlockNodes() gno.EnableDebug() + + logger.Debug("GnoVM packages preprocessed", + "elapsed", time.Since(start)) + } + return gnoStore +} + +var iavlBackupPrefix = []byte("init_iavl_backup:") + +func isStoreEmpty(st store.Store) bool { + iter := st.Iterator(nil, nil) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + return false } + return true +} + +func cachedStdlibLoad(stdlibsDir string, baseStore, iavlStore store.Store) gno.Store { + cachedStdlibOnce.Do(func() { + cachedStdlibBase = memdb.NewMemDB() + cachedStdlibIavl = memdb.NewMemDB() + + cachedGnoStore = gno.NewStore(nil, + dbadapter.StoreConstructor(cachedStdlibBase, types.StoreOptions{}), + dbadapter.StoreConstructor(cachedStdlibIavl, types.StoreOptions{})) + cachedGnoStore.SetNativeStore(stdlibs.NativeStore) + loadStdlib(stdlibsDir, cachedGnoStore) + }) + + itr := cachedStdlibBase.Iterator(nil, nil) + for ; itr.Valid(); itr.Next() { + baseStore.Set(itr.Key(), itr.Value()) + } + + itr = cachedStdlibIavl.Iterator(nil, nil) + for ; itr.Valid(); itr.Next() { + iavlStore.Set(itr.Key(), itr.Value()) + } + + alloc := gno.NewAllocator(maxAllocTx) + gs := gno.NewStore(alloc, baseStore, iavlStore) + gs.SetNativeStore(stdlibs.NativeStore) + gno.CopyCachesFromStore(gs, cachedGnoStore) + return gs +} + +var ( + cachedStdlibOnce sync.Once + cachedStdlibBase *memdb.MemDB + cachedStdlibIavl *memdb.MemDB + cachedGnoStore gno.Store +) + +func loadStdlib(stdlibsDir string, store gno.Store) { + stdlibInitList := stdlibs.InitOrder() + for _, lib := range stdlibInitList { + if lib == "testing" { + // XXX: testing is skipped for now while it uses testing-only packages + // like fmt and encoding/json + continue + } + loadStdlibPackage(lib, stdlibsDir, store) + } +} + +func loadStdlibPackage(pkgPath, stdlibsDir string, store gno.Store) { + stdlibPath := filepath.Join(stdlibsDir, pkgPath) + if !osm.DirExists(stdlibPath) { + // does not exist. + panic(fmt.Sprintf("failed loading stdlib %q: does not exist", pkgPath)) + } + memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) + if memPkg.IsEmpty() { + // no gno files are present + panic(fmt.Sprintf("failed loading stdlib %q: not a valid MemPackage", pkgPath)) + } + + m := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "gno.land/r/stdlibs/" + pkgPath, + // PkgPath: pkgPath, XXX why? + Output: os.Stdout, + Store: store, + }) + defer m.Release() + m.RunMemPackage(memPkg, true) } func (vm *VMKeeper) getGnoStore(ctx sdk.Context) gno.Store { @@ -131,7 +279,87 @@ func (vm *VMKeeper) getGnoStore(ctx sdk.Context) gno.Store { } } -var reRunPath = regexp.MustCompile(`gno\.land/r/g[a-z0-9]+/run`) +// Namespace can be either a user or crypto address. +var reNamespace = regexp.MustCompile(`^gno.land/(?:r|p)/([\.~_a-zA-Z0-9]+)`) + +// 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 { + const sysUsersPkg = "gno.land/r/sys/users" + + store := vm.getGnoStore(ctx) + + match := reNamespace.FindStringSubmatch(pkgPath) + switch len(match) { + case 0: + return ErrInvalidPkgPath(pkgPath) // no match + case 2: // ok + default: + panic("invalid pattern while matching pkgpath") + } + if len(match) != 2 { + return ErrInvalidPkgPath(pkgPath) + } + username := match[1] + + // if `sysUsersPkg` does not exist -> skip validation. + usersPkg := store.GetPackage(sysUsersPkg, false) + if usersPkg == nil { + return nil + } + + // Parse and run the files, construct *PV. + pkgAddr := gno.DerivePkgAddr(pkgPath) + msgCtx := stdlibs.ExecContext{ + ChainID: ctx.ChainID(), + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OrigCaller: creator.Bech32(), + OrigSendSpent: new(std.Coins), + OrigPkgAddr: pkgAddr.Bech32(), + // XXX: should we remove the banker ? + Banker: NewSDKBanker(vm, ctx), + EventLogger: ctx.EventLogger(), + } + + m := gno.NewMachineWithOptions( + gno.MachineOptions{ + PkgPath: "", + Output: os.Stdout, // XXX + Store: store, + Context: msgCtx, + Alloc: store.GetAllocator(), + MaxCycles: vm.maxCycles, + GasMeter: ctx.GasMeter(), + }) + defer m.Release() + + // call $sysUsersPkg.IsAuthorizedAddressForName("") + // We only need to check by name here, as address have already been check + mpv := gno.NewPackageNode("main", "main", nil).NewPackage() + m.SetActivePackage(mpv) + m.RunDeclaration(gno.ImportD("users", sysUsersPkg)) + x := gno.Call( + gno.Sel(gno.Nx("users"), "IsAuthorizedAddressForName"), + gno.Str(creator.String()), + gno.Str(username), + ) + + ret := m.Eval(x) + if len(ret) == 0 { + panic("call: invalid response length") + } + + useraddress := ret[0] + if useraddress.T.Kind() != gno.BoolKind { + panic("call: invalid response kind") + } + + if isAuthorized := useraddress.GetBool(); !isAuthorized { + return ErrUnauthorizedUser(username) + } + + return nil +} // AddPackage adds a package with given fileset. func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { @@ -155,13 +383,13 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { if pv := gnostore.GetPackage(pkgPath, false); pv != nil { return ErrInvalidPkgPath("package already exists: " + pkgPath) } - - if reRunPath.MatchString(pkgPath) { + if gno.ReGnoRunPath.MatchString(pkgPath) { return ErrInvalidPkgPath("reserved package name: " + pkgPath) } // Validate Gno syntax and type check. - if err := gno.TypeCheckMemPackage(memPkg, gnostore); err != nil { + format := true + if err := gno.TypeCheckMemPackage(memPkg, gnostore, format); err != nil { return ErrTypeCheck(err) } @@ -171,9 +399,9 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { // TODO: ACLs. // - if r/system/names does not exists -> skip validation. // - loads r/system/names data state. - // - lookup r/system/names.namespaces for `{r,p}/NAMES`. - // - check if caller is in Admins or Editors. - // - check if namespace is not in pause. + if err := vm.checkNamespacePermission(ctx, creator, pkgPath); err != nil { + return err + } err = vm.bank.SendCoins(ctx, creator, pkgAddr, deposit) if err != nil { @@ -218,6 +446,17 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { } }() m2.RunMemPackage(memPkg, true) + + // Log the telemetry + logTelemetry( + m2.GasMeter.GasConsumed(), + m2.Cycles, + attribute.KeyValue{ + Key: "operation", + Value: attribute.StringValue("m_addpkg"), + }, + ) + return nil } @@ -315,7 +554,19 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { res += "\n" } } + + // Log the telemetry + logTelemetry( + m.GasMeter.GasConsumed(), + m.Cycles, + attribute.KeyValue{ + Key: "operation", + Value: attribute.StringValue("m_call"), + }, + ) + res += "\n\n" // use `\n\n` as separator to separate results for single tx with multi msgs + return res, nil // TODO pay for gas? TODO see context? } @@ -343,7 +594,8 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Validate Gno syntax and type check. - if err = gno.TypeCheckMemPackage(memPkg, gnostore); err != nil { + format := false + if err = gno.TypeCheckMemPackage(memPkg, gnostore, format); err != nil { return "", ErrTypeCheck(err) } @@ -421,6 +673,17 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { }() m2.RunMain() res = buf.String() + + // Log the telemetry + logTelemetry( + m2.GasMeter.GasConsumed(), + m2.Cycles, + attribute.KeyValue{ + Key: "operation", + Value: attribute.StringValue("m_run"), + }, + ) + return res, nil } @@ -639,3 +902,35 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err return res, nil } } + +// logTelemetry logs the VM processing telemetry +func logTelemetry( + gasUsed int64, + cpuCycles int64, + attributes ...attribute.KeyValue, +) { + if !telemetry.MetricsEnabled() { + return + } + + // Record the operation frequency + metrics.VMExecMsgFrequency.Add( + context.Background(), + 1, + metric.WithAttributes(attributes...), + ) + + // Record the CPU cycles + metrics.VMCPUCycles.Record( + context.Background(), + cpuCycles, + metric.WithAttributes(attributes...), + ) + + // Record the gas used + metrics.VMGasUsed.Record( + context.Background(), + gasUsed, + metric.WithAttributes(attributes...), + ) +} diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index bd6f7ad88d1..a86ca5e4a97 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -17,6 +17,7 @@ import ( func TestVMKeeperAddPackage(t *testing.T) { env := setupTestEnv() ctx := env.ctx + vmk := env.vmk // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) @@ -30,10 +31,7 @@ func TestVMKeeperAddPackage(t *testing.T) { { Name: "test.gno", Body: `package test - -func Echo() string { - return "hello world" -}`, +func Echo() string {return "hello world"}`, }, } pkgPath := "gno.land/r/test" @@ -49,6 +47,16 @@ func Echo() string { assert.Error(t, err) assert.True(t, errors.Is(err, InvalidPkgPathError{})) + + // added package is formatted + store := vmk.getGnoStore(ctx) + memFile := store.GetMemFile("gno.land/r/test", "test.gno") + assert.NotNil(t, memFile) + expected := `package test + +func Echo() string { return "hello world" } +` + assert.Equal(t, expected, memFile.Body) } // Sending total send amount succeeds. @@ -366,6 +374,18 @@ func main() { // Call Run with stdlibs. func TestVMKeeperRunImportStdlibs(t *testing.T) { env := setupTestEnv() + testVMKeeperRunImportStdlibs(t, env) +} + +// Call Run with stdlibs, "cold" loading the standard libraries +func TestVMKeeperRunImportStdlibsColdStdlibLoad(t *testing.T) { + env := setupTestEnvCold() + testVMKeeperRunImportStdlibs(t, env) +} + +func testVMKeeperRunImportStdlibs(t *testing.T, env testEnv) { + t.Helper() + ctx := env.ctx // Give "addr1" some gnots. diff --git a/gno.land/pkg/sdk/vm/msgs.go b/gno.land/pkg/sdk/vm/msgs.go index e42babe1510..1fc01ab0e56 100644 --- a/gno.land/pkg/sdk/vm/msgs.go +++ b/gno.land/pkg/sdk/vm/msgs.go @@ -57,7 +57,7 @@ func (msg MsgAddPackage) ValidateBasic() error { return ErrInvalidPkgPath("missing package path") } if !msg.Deposit.IsValid() { - return std.ErrTxDecode("invalid deposit") + return std.ErrInvalidCoins("invalid deposit") } // XXX validate files. return nil @@ -113,9 +113,12 @@ func (msg MsgCall) ValidateBasic() error { if msg.Caller.IsZero() { return std.ErrInvalidAddress("missing caller address") } - if msg.PkgPath == "" { // XXX + if msg.PkgPath == "" { return ErrInvalidPkgPath("missing package path") } + if !gno.IsRealmPath(msg.PkgPath) { + return ErrInvalidPkgPath("pkgpath must be of a realm") + } if msg.Func == "" { // XXX return ErrInvalidExpr("missing function to call") } @@ -204,3 +207,44 @@ func (msg MsgRun) GetSigners() []crypto.Address { func (msg MsgRun) GetReceived() std.Coins { return msg.Send } + +//---------------------------------------- +// MsgNoop + +// MsgNoop - executes nothing +type MsgNoop struct { + Caller crypto.Address `json:"caller" yaml:"caller"` +} + +var _ std.Msg = MsgNoop{} + +func NewMsgNoop(caller crypto.Address) MsgNoop { + return MsgNoop{ + Caller: caller, + } +} + +// Implements Msg. +func (msg MsgNoop) Route() string { return RouterKey } + +// Implements Msg. +func (msg MsgNoop) Type() string { return "no_op" } + +// Implements Msg. +func (msg MsgNoop) ValidateBasic() error { + if msg.Caller.IsZero() { + return std.ErrInvalidAddress("missing caller address") + } + + return nil +} + +// Implements Msg. +func (msg MsgNoop) GetSignBytes() []byte { + return std.MustSortJSON(amino.MustMarshalJSON(msg)) +} + +// Implements Msg. +func (msg MsgNoop) GetSigners() []crypto.Address { + return []crypto.Address{msg.Caller} +} diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index b2e7fbecfc4..8649506cfe3 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -15,10 +15,12 @@ var Package = amino.RegisterPackage(amino.NewPackage( MsgCall{}, "m_call", MsgRun{}, "m_run", MsgAddPackage{}, "m_addpkg", // TODO rename both to MsgAddPkg? + MsgNoop{}, "m_noop", // errors InvalidPkgPathError{}, "InvalidPkgPathError", InvalidStmtError{}, "InvalidStmtError", InvalidExprError{}, "InvalidExprError", TypeCheckError{}, "TypeCheckError", + UnauthorizedUserError{}, "UnauthorizedUserError", )) diff --git a/gno.land/pkg/sdk/vm/package_test.go b/gno.land/pkg/sdk/vm/package_test.go new file mode 100644 index 00000000000..f19cc55aa20 --- /dev/null +++ b/gno.land/pkg/sdk/vm/package_test.go @@ -0,0 +1,49 @@ +package vm + +import ( + "reflect" + "strings" + "testing" + "unicode" + + "github.com/stretchr/testify/assert" +) + +func TestJSONSnakeCase(t *testing.T) { + t.Parallel() + for _, typ := range Package.Types { + assertJSONSnakeCase(t, typ.Type) + } +} + +func assertJSONSnakeCase(t *testing.T, typ reflect.Type) { + t.Helper() + + switch typ.Kind() { + case reflect.Array, reflect.Slice, reflect.Pointer: + assertJSONSnakeCase(t, typ.Elem()) + case reflect.Map: + assertJSONSnakeCase(t, typ.Key()) + assertJSONSnakeCase(t, typ.Elem()) + case reflect.Struct: + for i := 0; i < typ.NumField(); i++ { + fld := typ.Field(i) + if !fld.IsExported() { + continue + } + jt := fld.Tag.Get("json") + if jt == "" { + if fld.Anonymous { + assertJSONSnakeCase(t, fld.Type) + continue + } + t.Errorf("field %s.%s does not have a json tag but is exported", typ.Name(), fld.Name) + continue + } + has := strings.ContainsFunc(jt, unicode.IsUpper) + assert.False(t, has, + "field %s.%s contains uppercase symbols in json tag", typ.Name(), fld.Name) + assertJSONSnakeCase(t, fld.Type) + } + } +} diff --git a/gno.land/pkg/sdk/vm/vm.proto b/gno.land/pkg/sdk/vm/vm.proto index b99be0a85ff..9044cb70ca1 100644 --- a/gno.land/pkg/sdk/vm/vm.proto +++ b/gno.land/pkg/sdk/vm/vm.proto @@ -15,12 +15,22 @@ message m_call { repeated string args = 5; } +message m_run { + string caller = 1; + string send = 2; + std.MemPackage package = 3; +} + message m_addpkg { string creator = 1; std.MemPackage package = 2; string deposit = 3; } +message m_noop { + string caller = 1; +} + message InvalidPkgPathError { } @@ -28,4 +38,8 @@ message InvalidStmtError { } message InvalidExprError { +} + +message TypeCheckError { + repeated string errors = 1 [json_name = "Errors"]; } \ No newline at end of file diff --git a/gnovm/Makefile b/gnovm/Makefile index d07de6c33a2..5ff3af9c253 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -17,6 +17,7 @@ CGO_ENABLED ?= 0 export CGO_ENABLED # flags for `make fmt`. -w will write the result to the destination files. GOFMT_FLAGS ?= -w +GNOFMT_FLAGS ?= -w # flags for `make imports`. GOIMPORTS_FLAGS ?= $(GOFMT_FLAGS) # test suite flags. @@ -27,6 +28,9 @@ 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)" +# 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 ######################################## # Dev tools @@ -50,8 +54,8 @@ lint: .PHONY: fmt fmt: + go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . - $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) `find stdlibs -name "*.gno"` .PHONY: imports imports: @@ -66,6 +70,24 @@ test: _test.cmd _test.pkg _test.gnolang _test.cmd: go test ./cmd/... $(GOTEST_FLAGS) +# Run tests on ./cmd/, saving the result of the coverage in +# GOTEST_COVER_PROFILE. +.PHONY: test.cmd.coverage +test.cmd.coverage: + $(eval export TXTARCOVERDIR := $(shell mktemp -d --tmpdir gnovm-make.XXXXXXX)) + go test ./cmd/... -covermode atomic -test.gocoverdir='$(TXTARCOVERDIR)' $(GOTEST_FLAGS) + @echo "coverage results:" + go tool covdata percent -i="$(TXTARCOVERDIR)" + go tool covdata textfmt -v 1 -i="$(TXTARCOVERDIR)" -o '$(GOTEST_COVER_PROFILE)' + rm -rf "$(TXTARCOVERDIR)" + +# Run test.cmd.coverage, then view the result in the HTML browser render +# and delete the original file. +.PHONY: test.cmd.coverage_view +test.cmd.coverage_view: test.cmd.coverage + go tool cover -html='$(GOTEST_COVER_PROFILE)' + rm '$(GOTEST_COVER_PROFILE)' + .PHONY: _test.pkg _test.pkg: go test ./pkg/... $(GOTEST_FLAGS) @@ -74,13 +96,16 @@ _test.pkg: _test.gnolang: _test.gnolang.native _test.gnolang.stdlibs _test.gnolang.realm _test.gnolang.pkg0 _test.gnolang.pkg1 _test.gnolang.pkg2 _test.gnolang.other _test.gnolang.other:; go test tests/*.go -run "(TestFileStr|TestSelectors)" $(GOTEST_FLAGS) _test.gnolang.realm:; go test tests/*.go -run "TestFiles/^zrealm" $(GOTEST_FLAGS) -_test.gnolang.pkg0:; go test tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) -_test.gnolang.pkg1:; go test tests/*.go -run "TestPackages/regexp" $(GOTEST_FLAGS) -_test.gnolang.pkg2:; go test tests/*.go -run "TestPackages/bytes" $(GOTEST_FLAGS) +_test.gnolang.pkg0:; go test tests/*.go -run "TestStdlibs/(bufio|crypto|encoding|errors|internal|io|math|sort|std|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) +_test.gnolang.pkg1:; go test tests/*.go -run "TestStdlibs/regexp" $(GOTEST_FLAGS) +_test.gnolang.pkg2:; go test tests/*.go -run "TestStdlibs/bytes" $(GOTEST_FLAGS) _test.gnolang.native:; go test tests/*.go -test.short -run "TestFilesNative/" $(GOTEST_FLAGS) _test.gnolang.stdlibs:; go test tests/*.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) _test.gnolang.native.sync:; go test tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests $(GOTEST_FLAGS) _test.gnolang.stdlibs.sync:; go test tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests $(GOTEST_FLAGS) +# NOTE: challenges are current GnoVM bugs which are supposed to fail. +# If any of these tests pass, it should be moved to a normal test. +_test.gnolang.challenges:; go test tests/*.go -test.short -run 'TestChallenges$$/' $(GOTEST_FLAGS) ######################################## # Code gen diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go index 98e6fb07319..a1273d9ad59 100644 --- a/gnovm/cmd/gno/bug.go +++ b/gnovm/cmd/gno/bug.go @@ -23,8 +23,9 @@ Describe your issue in as much detail as possible here ### Your environment -* go version {{.GoVersion}} {{.Os}}/{{.Arch}} -* gno commit that causes this issue: {{.Commit}} +* Go version: {{.GoVersion}} +* OS and CPU architecture: {{.Os}}/{{.Arch}} +* Gno commit hash causing the issue: {{.Commit}} ### Steps to reproduce @@ -61,6 +62,17 @@ func newBugCmd(io commands.IO) *commands.Command { Name: "bug", ShortUsage: "bug", 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) +- OS and CPU architecture (example: linux/amd64) +- Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) + +The rest of the report consists of markdown sections such as ### Steps to reproduce +that you can edit. +`, }, cfg, func(_ context.Context, args []string) error { @@ -74,7 +86,7 @@ func (c *bugCfg) RegisterFlags(fs *flag.FlagSet) { &c.skipBrowser, "skip-browser", false, - "do not open the browser", + "output a prefilled issue template on the cli instead", ) } diff --git a/gnovm/cmd/gno/bug_test.go b/gnovm/cmd/gno/bug_test.go index 81231c3d580..516bfd4081b 100644 --- a/gnovm/cmd/gno/bug_test.go +++ b/gnovm/cmd/gno/bug_test.go @@ -14,7 +14,7 @@ func TestBugApp(t *testing.T) { }, { args: []string{"bug", "-skip-browser"}, - stdoutShouldContain: "go version go1.", + stdoutShouldContain: "Go version: go1.", }, } testMainCaseRun(t, tc) diff --git a/gnovm/cmd/gno/fmt.go b/gnovm/cmd/gno/fmt.go new file mode 100644 index 00000000000..7c5ad42c2b0 --- /dev/null +++ b/gnovm/cmd/gno/fmt.go @@ -0,0 +1,291 @@ +package main + +import ( + "bytes" + "context" + "errors" + "flag" + "fmt" + "go/format" + "go/parser" + "go/scanner" + "go/token" + "os" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnofmt" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/rogpeppe/go-internal/diff" +) + +type fmtCfg struct { + write bool + quiet bool + diff bool + verbose bool + imports bool + include fmtIncludes +} + +func newFmtCmd(io commands.IO) *commands.Command { + cfg := &fmtCfg{} + return commands.NewCommand( + commands.Metadata{ + Name: "fmt", + ShortUsage: "gno fmt [flags] [path ...]", + ShortHelp: "Run gno file formatter.", + LongHelp: "The `gno fmt` tool processes, formats, and cleans up `gno` source files.", + }, + cfg, + func(_ context.Context, args []string) error { + return execFmt(cfg, args, io) + }) +} + +func (c *fmtCfg) RegisterFlags(fs *flag.FlagSet) { + fs.BoolVar( + &c.write, + "w", + false, + "write result to (source) file instead of stdout", + ) + + fs.BoolVar( + &c.verbose, + "v", + false, + "verbose mode", + ) + + fs.BoolVar( + &c.quiet, + "q", + false, + "quiet mode", + ) + + fs.Var( + &c.include, + "include", + "specify additional directories containing packages to resolve", + ) + + fs.BoolVar( + &c.imports, + "imports", + true, + "attempt to format, resolve and sort file imports", + ) + + fs.BoolVar( + &c.diff, + "diff", + false, + "print and make the command fail if any diff is found", + ) +} + +type fmtProcessFileFunc func(file string, io commands.IO) []byte + +func execFmt(cfg *fmtCfg, args []string, io commands.IO) error { + if len(args) == 0 { + return flag.ErrHelp + } + + paths, err := targetsFromPatterns(args) + if err != nil { + return fmt.Errorf("unable to get targets paths from patterns: %w", err) + } + + files, err := gnoFilesFromArgs(paths) + if err != nil { + return fmt.Errorf("unable to gather gno files: %w", err) + } + + processFileFunc, err := fmtGetProcessFileFunc(cfg, io) + if err != nil { + return err + } + + errCount := fmtProcessFiles(cfg, files, processFileFunc, io) + if errCount > 0 { + if !cfg.verbose { + return commands.ExitCodeError(1) + } + + return fmt.Errorf("failed to format %d files", errCount) + } + + return nil +} + +func fmtGetProcessFileFunc(cfg *fmtCfg, io commands.IO) (fmtProcessFileFunc, error) { + if cfg.imports { + return fmtFormatFileImports(cfg, io) + } + return fmtFormatFile, nil +} + +func fmtProcessFiles(cfg *fmtCfg, files []string, processFile fmtProcessFileFunc, io commands.IO) int { + errCount := 0 + for _, file := range files { + if fmtProcessSingleFile(cfg, file, processFile, io) { + continue // ok + } + + errCount++ + } + return errCount +} + +// fmtProcessSingleFile process a single file and return false if any error occurred +func fmtProcessSingleFile(cfg *fmtCfg, file string, processFile fmtProcessFileFunc, io commands.IO) bool { + if cfg.verbose { + io.Printfln("processing %q", file) + } + + fi, err := os.Stat(file) + if err != nil { + io.ErrPrintfln("unable to stat %q: %v", file, err) + return false + } + + out := processFile(file, io) + if out == nil { + return false + } + + if cfg.diff && fmtProcessDiff(file, out, io) { + return false + } + if !cfg.write { + if !cfg.diff && !cfg.quiet { + io.Println(string(out)) + } + return true + } + + perms := fi.Mode() & os.ModePerm + if err = os.WriteFile(file, out, perms); err != nil { + io.ErrPrintfln("unable to write %q: %v", file, err) + return false + } + + return true +} + +func fmtProcessDiff(file string, data []byte, io commands.IO) bool { + oldFile, err := os.ReadFile(file) + if err != nil { + io.ErrPrintfln("unable to read %q for diffing: %v", file, err) + return true + } + + if d := diff.Diff(file, oldFile, file+".formatted", data); d != nil { + io.ErrPrintln(string(d)) + return true + } + + return false +} + +func fmtFormatFileImports(cfg *fmtCfg, io commands.IO) (fmtProcessFileFunc, error) { + r := gnofmt.NewFSResolver() + + gnoroot := gnoenv.RootDir() + + pkgHandler := func(path string, err error) error { + if err == nil { + return nil + } + + if !fmtPrintScannerError(err, io) { + io.ErrPrintfln("unable to load %q: %w", err.Error()) + } + + return nil + } + + // Load any additional packages supplied by the user + for _, include := range cfg.include { + absp, err := filepath.Abs(include) + if err != nil { + return nil, fmt.Errorf("unable to determine absolute path of %q: %w", include, err) + } + + if err := r.LoadPackages(absp, pkgHandler); err != nil { + return nil, fmt.Errorf("unable to load %q: %w", absp, err) + } + } + + // Load stdlibs + stdlibs := filepath.Join(gnoroot, "gnovm", "stdlibs") + if err := r.LoadPackages(stdlibs, pkgHandler); err != nil { + return nil, fmt.Errorf("unable to load %q: %w", stdlibs, err) + } + + // Load examples directory + examples := filepath.Join(gnoroot, "examples") + if err := r.LoadPackages(examples, pkgHandler); err != nil { + return nil, fmt.Errorf("unable to load %q: %w", examples, err) + } + + p := gnofmt.NewProcessor(r) + return func(file string, io commands.IO) []byte { + data, err := p.FormatFile(file) + if err == nil { + return data + } + + if !fmtPrintScannerError(err, io) { + io.ErrPrintfln("format error: %s", err.Error()) + } + + return nil + }, nil +} + +func fmtFormatFile(file string, io commands.IO) []byte { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, file, nil, parser.AllErrors|parser.ParseComments) + if err != nil { + fmtPrintScannerError(err, io) + return nil + } + + var buf bytes.Buffer + if err := format.Node(&buf, fset, node); err != nil { + io.ErrPrintfln("format error: %s", err.Error()) + return nil + } + + return buf.Bytes() +} + +func fmtPrintScannerError(err error, io commands.IO) bool { + // Get underlying parse error + for ; err != nil; err = errors.Unwrap(err) { + if scanErrors, ok := err.(scanner.ErrorList); ok { + for _, e := range scanErrors { + io.ErrPrintln(e) + } + + return true + } + } + + return false +} + +type fmtIncludes []string + +func (i fmtIncludes) String() string { + return strings.Join(i, ",") +} + +func (i *fmtIncludes) Set(path string) error { + *i = append(*i, path) + return nil +} diff --git a/gnovm/cmd/gno/fmt_test.go b/gnovm/cmd/gno/fmt_test.go new file mode 100644 index 00000000000..3b3d1bd51a6 --- /dev/null +++ b/gnovm/cmd/gno/fmt_test.go @@ -0,0 +1,18 @@ +package main + +import "testing" + +func TestFmtApp(t *testing.T) { + tc := []testMainCase{ + { + args: []string{"fmt"}, + errShouldBe: "flag: help requested", + }, { + args: []string{"fmt", "../../tests/integ/unformated/missing_import.gno"}, + stdoutShouldContain: "strconv", + }, + + // XXX: more complex output are tested in `testdata/gno_test/fmt_*.txtar`. + } + testMainCaseRun(t, tc) +} diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index e2a7e53fb69..6c497c7e2c0 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -5,6 +5,8 @@ import ( "errors" "flag" "fmt" + "go/scanner" + "io" "os" "path/filepath" "regexp" @@ -18,9 +20,8 @@ import ( ) type lintCfg struct { - verbose bool - rootDir string - setExitStatus int + verbose bool + rootDir string // min_confidence: minimum confidence of a problem to print it (default 0.8) // auto-fix: apply suggested fixes automatically. } @@ -46,7 +47,6 @@ func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar(&c.verbose, "v", false, "verbose output when lintning") fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)") - fs.IntVar(&c.setExitStatus, "set-exit-status", 1, "set exit status to 1 if any issues are found") } func execLint(cfg *lintCfg, args []string, io commands.IO) error { @@ -62,16 +62,12 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgPaths, err := gnoPackagesFromArgs(args) + pkgPaths, err := gnoPackagesFromArgsRecursively(args) if err != nil { return fmt.Errorf("list packages from args: %w", err) } hasError := false - addIssue := func(issue lintIssue) { - hasError = true - fmt.Fprint(io.Err(), issue.String()+"\n") - } for _, pkgPath := range pkgPaths { if verbose { @@ -81,16 +77,18 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { // Check if 'gno.mod' exists gnoModPath := filepath.Join(pkgPath, "gno.mod") if !osm.FileExists(gnoModPath) { - addIssue(lintIssue{ + hasError = true + issue := lintIssue{ Code: lintNoGnoMod, Confidence: 1, Location: pkgPath, Msg: "missing 'gno.mod' file", - }) + } + fmt.Fprint(io.Err(), issue.String()+"\n") } // Handle runtime errors - catchRuntimeError(pkgPath, addIssue, func() { + hasError = catchRuntimeError(pkgPath, io.Err(), func() { stdout, stdin, stderr := io.Out(), io.In(), io.Err() testStore := tests.TestStore( rootDir, "", @@ -130,13 +128,13 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } tm.RunFiles(testfiles.Files...) - }) + }) || hasError // TODO: Add more checkers } - if hasError && cfg.setExitStatus != 0 { - os.Exit(cfg.setExitStatus) + if hasError { + return commands.ExitCodeError(1) } return nil @@ -164,47 +162,33 @@ func guessSourcePath(pkg, source string) string { // XXX: Ideally, error handling should encapsulate location details within a dedicated error type. var reParseRecover = regexp.MustCompile(`^([^:]+):(\d+)(?::\d+)?:? *(.*)$`) -func catchRuntimeError(pkgPath string, addIssue func(issue lintIssue), action func()) { +func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (hasError bool) { defer func() { // Errors catched here mostly come from: gnovm/pkg/gnolang/preprocess.go r := recover() if r == nil { return } - - var err error + hasError = true switch verr := r.(type) { case *gno.PreprocessError: - err = verr.Unwrap() + err := verr.Unwrap() + fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n") + case scanner.ErrorList: + for _, err := range verr { + fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n") + } case error: - err = verr + fmt.Fprint(stderr, issueFromError(pkgPath, verr).String()+"\n") case string: - err = errors.New(verr) + fmt.Fprint(stderr, issueFromError(pkgPath, errors.New(verr)).String()+"\n") default: panic(r) } - - var issue lintIssue - issue.Confidence = 1 - issue.Code = lintGnoError - - parsedError := strings.TrimSpace(err.Error()) - parsedError = strings.TrimPrefix(parsedError, pkgPath+"/") - - matches := reParseRecover.FindStringSubmatch(parsedError) - if len(matches) == 4 { - sourcepath := guessSourcePath(pkgPath, matches[1]) - issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2]) - issue.Msg = strings.TrimSpace(matches[3]) - } else { - issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath)) - issue.Msg = err.Error() - } - - addIssue(issue) }() action() + return } type lintCode int @@ -229,3 +213,23 @@ func (i lintIssue) String() string { // TODO: consider crafting a doc URL based on Code. return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code) } + +func issueFromError(pkgPath string, err error) lintIssue { + var issue lintIssue + issue.Confidence = 1 + issue.Code = lintGnoError + + parsedError := strings.TrimSpace(err.Error()) + parsedError = strings.TrimPrefix(parsedError, pkgPath+"/") + + matches := reParseRecover.FindStringSubmatch(parsedError) + if len(matches) == 4 { + sourcepath := guessSourcePath(pkgPath, matches[1]) + issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2]) + issue.Msg = strings.TrimSpace(matches[3]) + } else { + issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath)) + issue.Msg = err.Error() + } + return issue +} diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index 5773ae0a06f..a5c0319cd00 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -8,22 +8,30 @@ func TestLintApp(t *testing.T) { args: []string{"lint"}, errShouldBe: "flag: help requested", }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run_main/"}, + args: []string{"lint", "../../tests/integ/run_main/"}, stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).", + errShouldBe: "exit code: 1", }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, + args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, stderrShouldContain: "undefined_variables_test.gno:6: name toto not declared (code=2)", + errShouldBe: "exit code: 1", }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/package_not_declared/main.gno"}, + args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, stderrShouldContain: "main.gno:4: name fmt not declared (code=2).", + errShouldBe: "exit code: 1", }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run_main/"}, + args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"}, + stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5: expected ';', found example (code=2).\n../../tests/integ/several-lint-errors/main.gno:6", + errShouldBe: "exit code: 1", + }, { + args: []string{"lint", "../../tests/integ/run_main/"}, stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).", + errShouldBe: "exit code: 1", }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/minimalist_gnomod/"}, + args: []string{"lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/invalid_module_name/"}, + args: []string{"lint", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid }, diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 8b77cfd2a10..7a5799f2835 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -34,7 +34,7 @@ func newGnocliCmd(io commands.IO) *commands.Command { newDocCmd(io), newEnvCmd(io), newBugCmd(io), - // fmt -- gofmt + newFmtCmd(io), // graph // vendor -- download deps from the chain in vendor/ // list -- list packages diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 6c310cd7425..fec1b0ab2c1 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -14,13 +14,9 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" + "go.uber.org/multierr" ) -type modDownloadCfg struct { - remote string - verbose bool -} - func newModCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ @@ -73,15 +69,17 @@ func newModInitCmd() *commands.Command { } func newModTidy(io commands.IO) *commands.Command { + cfg := &modTidyCfg{} + return commands.NewCommand( commands.Metadata{ Name: "tidy", - ShortUsage: "tidy", + ShortUsage: "tidy [flags]", ShortHelp: "add missing and remove unused modules", }, - commands.NewEmptyConfig(), + cfg, func(_ context.Context, args []string) error { - return execModTidy(args, io) + return execModTidy(cfg, args, io) }, ) } @@ -124,11 +122,16 @@ For example: ) } +type modDownloadCfg struct { + remote string + verbose bool +} + func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.remote, "remote", - "test3.gno.land:36657", + "test3.gno.land:26657", "remote for fetching gno modules", ) @@ -211,7 +214,27 @@ func execModInit(args []string) error { return nil } -func execModTidy(args []string, io commands.IO) error { +type modTidyCfg struct { + verbose bool + recursive bool +} + +func (c *modTidyCfg) RegisterFlags(fs *flag.FlagSet) { + fs.BoolVar( + &c.verbose, + "v", + false, + "verbose output when running", + ) + fs.BoolVar( + &c.recursive, + "recursive", + false, + "walk subdirs for gno.mod files", + ) +} + +func execModTidy(cfg *modTidyCfg, args []string, io commands.IO) error { if len(args) > 0 { return flag.ErrHelp } @@ -220,7 +243,34 @@ func execModTidy(args []string, io commands.IO) error { if err != nil { return err } - fname := filepath.Join(wd, "gno.mod") + + if cfg.recursive { + pkgs, err := gnomod.ListPkgs(wd) + if err != nil { + return err + } + var errs error + for _, pkg := range pkgs { + err := modTidyOnce(cfg, wd, pkg.Dir, io) + errs = multierr.Append(errs, err) + } + return errs + } + + // XXX: recursively check parents if no $PWD/gno.mod + return modTidyOnce(cfg, wd, wd, io) +} + +func modTidyOnce(cfg *modTidyCfg, wd, pkgdir string, io commands.IO) error { + fname := filepath.Join(pkgdir, "gno.mod") + relpath, err := filepath.Rel(wd, fname) + if err != nil { + return err + } + if cfg.verbose { + io.ErrPrintfln("%s", relpath) + } + gm, err := gnomod.ParseGnoMod(fname) if err != nil { return err @@ -231,7 +281,7 @@ func execModTidy(args []string, io commands.IO) error { gm.DropRequire(r.Mod.Path) } - imports, err := getGnoPackageImports(wd) + imports, err := getGnoPackageImports(pkgdir) if err != nil { return err } @@ -241,6 +291,9 @@ func execModTidy(args []string, io commands.IO) error { continue } gm.AddRequire(im, "v0.0.0-latest") + if cfg.verbose { + io.ErrPrintfln(" %s", im) + } } gm.Write(fname) diff --git a/gnovm/cmd/gno/repl.go b/gnovm/cmd/gno/repl.go index 0110b44b58a..8a6274eb6eb 100644 --- a/gnovm/cmd/gno/repl.go +++ b/gnovm/cmd/gno/repl.go @@ -74,6 +74,7 @@ func execRepl(cfg *replCfg, args []string) error { // gno> import "gno.land/p/demo/avl" // import the p/demo/avl package // gno> func a() string { return "a" } // declare a new function named a // gno> /src // print current generated source +// gno> /debug // activate the GnoVM debugger // gno> /editor // enter in multi-line mode, end with ';' // gno> /reset // remove all previously inserted code // gno> println(a()) // print the result of calling a() @@ -141,6 +142,8 @@ func handleInput(r *repl.Repl, input string) error { switch strings.TrimSpace(input) { case "/reset": r.Reset() + case "/debug": + r.Debug() case "/src": fmt.Fprintln(os.Stdout, r.Src()) case "/exit": diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 0c5218613a9..39306d59cb0 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -5,6 +5,7 @@ import ( "errors" "flag" "fmt" + "io" "os" "path/filepath" "strings" @@ -102,7 +103,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { } // read files - files, err := parseFiles(args) + files, err := parseFiles(args, stderr) if err != nil { return err } @@ -135,15 +136,16 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { return nil } -func parseFiles(fnames []string) ([]*gno.FileNode, error) { +func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) { files := make([]*gno.FileNode, 0, len(fnames)) + var hasError bool for _, fname := range fnames { if s, err := os.Stat(fname); err == nil && s.IsDir() { subFns, err := listNonTestFiles(fname) if err != nil { return nil, err } - subFiles, err := parseFiles(subFns) + subFiles, err := parseFiles(subFns, stderr) if err != nil { return nil, err } @@ -154,7 +156,14 @@ func parseFiles(fnames []string) ([]*gno.FileNode, error) { // in either case not a file we can parse. return nil, err } - files = append(files, gno.MustReadFile(fname)) + + hasError = catchRuntimeError(fname, stderr, func() { + files = append(files, gno.MustReadFile(fname)) + }) + } + + if hasError { + return nil, commands.ExitCodeError(1) } return files, nil } diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index f78c15edb34..79a873cdfe5 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -73,7 +73,11 @@ func TestRunApp(t *testing.T) { }, { args: []string{"run", "-debug-addr", "invalidhost:17538", "../../tests/integ/debugger/sample.gno"}, - errShouldContain: "listen tcp: lookup invalidhost", + errShouldContain: "listen tcp", + }, + { + args: []string{"run", "../../tests/integ/invalid_assign/main.gno"}, + recoverShouldContain: "cannot use bool as main.C without explicit conversion", }, // TODO: a test file // TODO: args diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 8b48e10d422..5884463a552 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -20,7 +20,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" @@ -34,7 +33,6 @@ type testCfg struct { rootDir string run string timeout time.Duration - transpile bool // TODO: transpile should be the default, but it needs to automatically transpile dependencies in memory. updateGoldenTests bool printRuntimeMetrics bool withNativeFallback bool @@ -110,13 +108,6 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { "verbose output when running", ) - fs.BoolVar( - &c.transpile, - "transpile", - false, - "transpile gno to go before testing", - ) - fs.BoolVar( &c.updateGoldenTests, "update-golden-tests", @@ -165,21 +156,6 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { return flag.ErrHelp } - verbose := cfg.verbose - - tempdirRoot, err := os.MkdirTemp("", "gno-transpile") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(tempdirRoot) - - // go.mod - modPath := filepath.Join(tempdirRoot, "go.mod") - err = makeTestGoMod(modPath, transpiler.ImportPrefix, "1.21") - if err != nil { - return fmt.Errorf("write .mod file: %w", err) - } - // guess opts.RootDir if cfg.rootDir == "" { cfg.rootDir = gnoenv.RootDir() @@ -209,43 +185,6 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount := 0 testErrCount := 0 for _, pkg := range subPkgs { - if cfg.transpile { - if verbose { - io.ErrPrintfln("=== PREC %s", pkg.Dir) - } - transpileOpts := newTranspileOptions(&transpileCfg{ - output: tempdirRoot, - }) - err := transpilePkg(importPath(pkg.Dir), transpileOpts) - if err != nil { - io.ErrPrintln(err) - io.ErrPrintln("FAIL") - io.ErrPrintfln("FAIL %s", pkg.Dir) - io.ErrPrintln("FAIL") - - buildErrCount++ - continue - } - - if verbose { - io.ErrPrintfln("=== BUILD %s", pkg.Dir) - } - tempDir, err := ResolvePath(tempdirRoot, importPath(pkg.Dir)) - if err != nil { - return errors.New("cannot resolve build dir") - } - err = goBuildFileOrPkg(tempDir, defaultTranspileCfg) - if err != nil { - io.ErrPrintln(err) - io.ErrPrintln("FAIL") - io.ErrPrintfln("FAIL %s", pkg.Dir) - io.ErrPrintln("FAIL") - - buildErrCount++ - continue - } - } - if len(pkg.TestGnoFiles) == 0 && len(pkg.FiletestGnoFiles) == 0 { io.ErrPrintfln("? %s \t[no test files]", pkg.Dir) continue @@ -319,13 +258,21 @@ func gnoTestPkg( if gnoPkgPath == "" { // unable to read pkgPath from gno.mod, generate a random realm path io.ErrPrintfln("--- WARNING: unable to read package path from gno.mod or gno root directory; try creating a gno.mod file") - gnoPkgPath = transpiler.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = gno.RealmPathPrefix + random.RandStr(8) } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) // tfiles, ifiles := gno.ParseMemPackageTests(memPkg) - tfiles, ifiles := parseMemPackageTests(memPkg) + var tfiles, ifiles *gno.FileSet + + hasError := catchRuntimeError(gnoPkgPath, stderr, func() { + tfiles, ifiles = parseMemPackageTests(memPkg) + }) + + if hasError { + return commands.ExitCodeError(1) + } testPkgName := getPkgNameFromFileset(ifiles) // run test files in pkg @@ -639,7 +586,7 @@ func parseMemPackageTests(memPkg *std.MemPackage) (tset, itset *gno.FileSet) { } n, err := gno.ParseFile(mfile.Name, mfile.Body) if err != nil { - panic(errors.Wrap(err, "parsing file "+mfile.Name)) + panic(err) } if n == nil { panic("should not happen") diff --git a/gnovm/cmd/gno/test_test.go b/gnovm/cmd/gno/test_test.go deleted file mode 100644 index 98320f41cc9..00000000000 --- a/gnovm/cmd/gno/test_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "os" - "strconv" - "testing" - - "github.com/gnolang/gno/gnovm/pkg/integration" - "github.com/rogpeppe/go-internal/testscript" - "github.com/stretchr/testify/require" -) - -func Test_ScriptsTest(t *testing.T) { - updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) - p := testscript.Params{ - UpdateScripts: updateScripts, - Dir: "testdata/gno_test", - } - - if coverdir, ok := integration.ResolveCoverageDir(); ok { - err := integration.SetupTestscriptsCoverage(&p, coverdir) - require.NoError(t, err) - } - - err := integration.SetupGno(&p, t.TempDir()) - require.NoError(t, err) - - testscript.Run(t, p) -} diff --git a/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar b/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar new file mode 100644 index 00000000000..14f85227335 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar @@ -0,0 +1,10 @@ +gno fmt noimport.gno +cmp stdout stdout.golden.gno +cmp stderr stderr.golden + +-- noimport.gno -- +package hello +-- stdout.golden.gno -- +package hello + +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar b/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar new file mode 100644 index 00000000000..bc186f98b6f --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar @@ -0,0 +1,30 @@ +# two files with with one declaration on the side + +gno fmt cleaning.gno +cmp stdout stdout.golden +cmp stderr stderr.golden + +-- cleaning.gno -- +package testdata + +import ( + "std" + + "gno.land/r/hello" +) + +var yes = rand.Val + +-- otherfile.gno -- +package testdata + +type S struct {} + +var rand = &S{} + +-- stdout.golden -- +package testdata + +var yes = rand.Val + +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/include.txtar b/gnovm/cmd/gno/testdata/gno_fmt/include.txtar new file mode 100644 index 00000000000..92767983705 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/include.txtar @@ -0,0 +1,31 @@ +# Test fmt with include and verbose enabled +gno fmt -include $WORK/pkg file.gno +cmp stdout stdout.golden +cmp stderr stderr.golden + +-- pkg/mypkg/file.gno -- +package mypkg + +func HelloFromMyPkg() string { + return "hello gnoland" +} + +-- pkg/mypkg/gno.mod -- +module gno.land/r/test/mypkg + +-- file.gno -- +package testdata + +var myVar = mypkg.HelloFromMyPkg() + +-- gno.mod -- +module gno.land/r/test/mypkg2 + +-- stdout.golden -- +package testdata + +import "gno.land/r/test/mypkg" + +var myVar = mypkg.HelloFromMyPkg() + +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar b/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar new file mode 100644 index 00000000000..9e16f33591d --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar @@ -0,0 +1,96 @@ +# Test format complex files with advanced declarations + +gno fmt file1.gno +cmp stdout stdout.golden +cmp stderr stderr.golden + +# do it again, output should be identitical +gno fmt file1.gno +cmp stdout stdout.golden +cmp stderr stderr.golden + +-- file1.gno -- +package testdata + +import ( + "std" + + // This comment should stay above the declaration + avl "gno.land/my/import/does/not/exist" + "doesnotexist" +) + + +// Pkg Is already imported +var myVar = avl.Node{} + +// Pkg exist and should be imported + var myVar2 = std.Banker{} + +// This one doesn't exist +var myVar3 = doesnotexistpkg.Bis{} + +// Package should exist but not the declaration + var myVar4 = io.DoesnNotExist{} + +// Declaration exist in a side file and should not be imported +var myVar5 = math.Sqrt(42) + +func myBlog() *blog.Blog { + myVar4 := time.Time{} + + var subFunc = func() { + // More complex catch declaration + println(string(ufmt.Sprintf("hello gno"))) + } + + return &blog.Blog{} +} + +-- file2.gno -- +package testdata + +type MathS struct {} + +var math = MathS{} + +-- stdout.golden -- +package testdata + +import ( + "std" + "time" + + // This comment should stay above the declaration + avl "gno.land/my/import/does/not/exist" + "gno.land/p/demo/blog" + "gno.land/p/demo/ufmt" +) + +// Pkg Is already imported +var myVar = avl.Node{} + +// Pkg exist and should be imported +var myVar2 = std.Banker{} + +// This one doesn't exist +var myVar3 = doesnotexistpkg.Bis{} + +// Package should exist but not the declaration +var myVar4 = io.DoesnNotExist{} + +// Declaration exist in a side file and should not be imported +var myVar5 = math.Sqrt(42) + +func myBlog() *blog.Blog { + myVar4 := time.Time{} + + var subFunc = func() { + // More complex catch declaration + println(string(ufmt.Sprintf("hello gno"))) + } + + return &blog.Blog{} +} + +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar b/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar new file mode 100644 index 00000000000..56fca6ddbd7 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar @@ -0,0 +1,39 @@ +# Test format without trying to resolve import + +gno fmt -imports=false cleaning.gno +cmp stdout stdout.golden +cmp stderr stderr.golden + +-- cleaning.gno -- +package testdata + +import ( + "std" + + "gno.land/r/hello" + + + +) + + var yes = rand.Val + +-- otherfile.gno -- +package testdata + +type S struct {} + +var rand = &S{} + +-- stdout.golden -- +package testdata + +import ( + "std" + + "gno.land/r/hello" +) + +var yes = rand.Val + +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar b/gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar new file mode 100644 index 00000000000..083bdce8769 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar @@ -0,0 +1,19 @@ +# Test error parsing + +! gno fmt file1.gno +cmp stdout stdout.golden +stderr 'file1.gno:6:1: expected declaration, found myVar' +stderr 'file1.gno:9:12: expected type, found' + +-- file1.gno -- +package testdata + +import "gno.land/p/demo/avl" + +// invalid syntax +myVar + avl.Node + +// invalid syntax +var myVal2 := "hello" + +-- stdout.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar b/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar new file mode 100644 index 00000000000..50d0970497e --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar @@ -0,0 +1,53 @@ +# Test error parsing + +gno fmt -include v1 -include v2 v2/file_filetest.gno +cmp stdout stdout.golden +cmp stderr stderr.golden + +-- v1/file.gno -- +package v1 + +func Get(v string) string { + return "v1:"+ v +} + +-- v1/gno.mod -- +module "gno.land/r/dev/shadow/v1" + +-- v2/file.gno -- +package v1 + +func Get(v string) string { + return "v2:"+ v +} + + +-- v2/file_filetest.gno -- +package main + +import ( + // should be valid + "gno.land/r/dev/shadow/v2" +) + +func main() { + println("a", v1.Get("a")) +} + +-- v2/gno.mod -- +module "gno.land/r/dev/shadow/v2" + + +-- stdout.golden -- +package main + +import ( + // should be valid + "gno.land/r/dev/shadow/v2" +) + +func main() { + println("a", v1.Get("a")) +} + +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar b/gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar similarity index 82% rename from gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar rename to gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar index 946e1bcba35..fc4039d38c6 100644 --- a/gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar +++ b/gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar @@ -16,4 +16,4 @@ func main() { -- stdout.golden -- -- stderr.golden -- -bad_file.gno:1: unknown import path python (code=2). +bad_file.gno:3: unknown import path python (code=2). diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar b/gnovm/cmd/gno/testdata/gno_lint/file_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar rename to gnovm/cmd/gno/testdata/gno_lint/file_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_file_error_txtar b/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/lint_file_error_txtar rename to gnovm/cmd/gno/testdata/gno_lint/file_error_txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_no_error.txtar b/gnovm/cmd/gno/testdata/gno_lint/no_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/lint_no_error.txtar rename to gnovm/cmd/gno/testdata/gno_lint/no_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar b/gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar rename to gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_not_declared.txtar b/gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/lint_not_declared.txtar rename to gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar index 6d7d73a5e20..2cdff48c9e8 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar @@ -5,9 +5,4 @@ gno test . ! stdout .+ stderr '\? \. \[no test files\]' -! gno test --transpile . - -! stdout .+ -stderr 'expected ''package'', found ''EOF''' - -- empty.gno -- diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar index fa14c412548..b15d35b20b3 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar @@ -5,10 +5,5 @@ ! stdout .+ stderr 'expected ''package'', found ''EOF''' -! gno test --transpile . - -! stdout .+ -stderr 'expected ''package'', found ''EOF''' - -- empty.gno -- -- empty_test.gno -- diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar index 3ffe7adcedd..5c44b5d3772 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar @@ -5,10 +5,5 @@ ! stdout .+ stderr 'expected ''package'', found ''EOF''' -! gno test --transpile . - -! stdout .+ -stderr 'expected ''package'', found ''EOF''' - -- empty.gno -- -- empty_filetest.gno -- diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar index 5617996c231..91431e4f7bb 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar @@ -6,14 +6,6 @@ stdout 'Machine.RunMain\(\) panic: beep boop' stderr '=== RUN file/failing_filetest.gno' stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' -! gno test -v --transpile . - -stdout 'Machine.RunMain\(\) panic: beep boop' -stderr '=== PREC \.' -stderr '=== BUILD \.' -stderr '=== RUN file/failing_filetest.gno' -stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' - -- failing.gno -- package failing diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar index 0a7fd1f5ae9..e1a86b84fdd 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar @@ -7,13 +7,6 @@ stderr '=== RUN TestAlwaysFailing' stderr '--- FAIL: TestAlwaysFailing' stderr 'FAIL: 0 build errors, 1 test errors' -! gno test -v --transpile . - -! stdout .+ -stderr '=== RUN TestAlwaysFailing' -stderr '--- FAIL: TestAlwaysFailing' -stderr 'FAIL: 0 build errors, 1 test errors' - -- failing.gno -- package failing diff --git a/gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar b/gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar new file mode 100644 index 00000000000..6f316fab9e0 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar @@ -0,0 +1,25 @@ +# Test format with write flag + +gno fmt -w file1.gno +cmp file1.gno file1.gno.golden +cmp stderr stderr.golden + +-- file1.gno -- +package testdata + +import ( + "std" + "doesnotexist" +) + + var myVar1 = std.Banker{} + +-- file1.gno.golden -- +package testdata + +import ( + "std" +) + +var myVar1 = std.Banker{} +-- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar new file mode 100644 index 00000000000..9d935df74c2 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar @@ -0,0 +1,71 @@ +# Set up GNOROOT in the current directory. +mkdir $WORK/gnovm +symlink $WORK/gnovm/stdlibs -> $GNOROOT/gnovm/stdlibs +env GNOROOT=$WORK + +gno test -v ./examples/gno.land/r/demo/realm2 + +stderr '=== RUN TestDo' +stderr '--- PASS: TestDo.*' + +stderr '=== RUN file/realm2_filetest.gno' +stderr '--- PASS: file/realm2_filetest.*' + +-- examples/gno.land/p/demo/counter/gno.mod -- +module gno.land/p/demo/counter + +-- examples/gno.land/p/demo/counter/counter.gno -- +package counter + +type Counter struct { + n int +} + +func (c *Counter) Inc() { + c.n++ +} + +-- examples/gno.land/r/demo/realm1/realm1.gno -- +package realm1 + +import "gno.land/p/demo/counter" + +var c = counter.Counter{} + +func GetCounter() *counter.Counter { + return &c +} + +-- examples/gno.land/r/demo/realm2/realm2.gno -- +package realm2 + +import ( + "gno.land/r/demo/realm1" +) + +func Do() { + realm1.GetCounter().Inc() +} + +-- examples/gno.land/r/demo/realm2/realm2_filetest.gno -- +// PKGPATH: gno.land/r/tests +package tests + +import "gno.land/r/demo/realm2" + +func main() { + realm2.Do() + println("OK") +} + +// Output: +// OK + +-- examples/gno.land/r/demo/realm2/realm2_test.gno -- +package realm2 + +import "testing" + +func TestDo(t *testing.T) { + Do() +} diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar index 6845a5706c2..99e6fccd42d 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar @@ -32,14 +32,21 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/x" // } // }, // "Values": [ // { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { // "T": { // "@type": "/gno.FuncType", // "Params": [], @@ -62,9 +69,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "6", -// "Nonce": "0", // "PkgPath": "gno.land/r/x" // } // }, @@ -74,13 +81,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } // } // ] // } diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar index 19c78a93870..3d27ab4fde0 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar @@ -47,14 +47,21 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/x" // } // }, // "Values": [ // { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { // "T": { // "@type": "/gno.FuncType", // "Params": [], @@ -77,9 +84,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "6", -// "Nonce": "0", // "PkgPath": "gno.land/r/x" // } // }, @@ -89,13 +96,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } // } // ] // } diff --git a/gnovm/cmd/gno/testdata/gno_test/test_with-native-fallback.txtar b/gnovm/cmd/gno/testdata/gno_test/test_with-native-fallback.txtar index 42dae650a7e..0954d1dd932 100644 --- a/gnovm/cmd/gno/testdata/gno_test/test_with-native-fallback.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/test_with-native-fallback.txtar @@ -4,7 +4,7 @@ ! stdout .+ stderr 'panic: unknown import path net \[recovered\]' -stderr ' panic: gno.land/r/\w{8}/contract.gno:1: unknown import path net' +stderr ' panic: gno.land/r/\w{8}/contract.gno:3:1: unknown import path net' gno test -v --with-native-fallback . diff --git a/gnovm/cmd/gno/testdata/gno_test/unknow_lib.txtar b/gnovm/cmd/gno/testdata/gno_test/unknow_lib.txtar index 33fc85fd35f..15125f695f5 100644 --- a/gnovm/cmd/gno/testdata/gno_test/unknow_lib.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/unknow_lib.txtar @@ -4,13 +4,13 @@ ! stdout .+ stderr 'panic: unknown import path foobarbaz \[recovered\]' -stderr ' panic: gno.land/r/\w{8}/contract.gno:1: unknown import path foobarbaz' +stderr ' panic: gno.land/r/\w{8}/contract.gno:3:1: unknown import path foobarbaz' ! gno test -v --with-native-fallback . ! stdout .+ stderr 'panic: unknown import path foobarbaz \[recovered\]' -stderr ' panic: gno.land/r/\w{8}/contract.gno:1: unknown import path foobarbaz' +stderr ' panic: gno.land/r/\w{8}/contract.gno:3:1: unknown import path foobarbaz' -- contract.gno -- package contract diff --git a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar deleted file mode 100644 index c07c670f721..00000000000 --- a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar +++ /dev/null @@ -1,36 +0,0 @@ -# Run gno transpile with -skip-fmt flag -# NOTE(tb): this flag doesn't actually prevent the code format, because -# `gnolang.Transpile()` calls `format.Node()`. - -gno transpile -skip-fmt . - -! stdout .+ -! stderr .+ - -cmp main.gno.gen.go main.gno.gen.go.golden -cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden - --- main.gno -- -package main - -func main(){} - --- sub/sub.gno -- -package sub - --- main.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line main.gno:1:1 -package main - -func main() {} --- sub/sub.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line sub.gno:1:1 -package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar deleted file mode 100644 index 110d04959c0..00000000000 --- a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar +++ /dev/null @@ -1,38 +0,0 @@ -# Run gno transpile with -gobuild flag - -gno transpile -gobuild . - -! stdout .+ -! stderr .+ - -cmp main.gno.gen.go main.gno.gen.go.golden -cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden - --- main.gno -- -package main - -func main() { - var x = 1 - _=x -} --- sub/sub.gno -- -package sub --- main.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line main.gno:1:1 -package main - -func main() { - var x = 1 - _ = x -} --- sub/sub.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line sub.gno:1:1 -package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar similarity index 60% rename from gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar index 79d4d6a4a2c..0c51012feb7 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar @@ -1,10 +1,10 @@ -# Run gno transpile with gno files with whitelist errors +# Run gno transpile with gno files with an invalid import path ! gno transpile . ! stdout .+ -stderr '^main.gno:5:2: import "xxx" is not in the whitelist$' -stderr '^sub/sub.gno:3:8: import "xxx" is not in the whitelist$' +stderr '^main.gno:5:2: import "xxx" does not exist$' +stderr '^sub/sub.gno:3:8: import "xxx" does not exist$' stderr '^2 transpile error\(s\)$' # no *.gen.go files are created diff --git a/gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar new file mode 100644 index 00000000000..40bb1ecb98a --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar @@ -0,0 +1,31 @@ +# Run gno transpile with -gobuild flag on an individual file + +gno transpile -gobuild -v main.gno + +! stdout .+ +cmp stderr stderr.golden + +cmp main.gno.gen.go main.gno.gen.go.golden + +-- stderr.golden -- +main.gno +main.gno [build] +-- main.gno -- +package main + +func main() { + var x = 1 + _=x +} +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + var x = 1 + _ = x +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar new file mode 100644 index 00000000000..2eacfb9de60 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar @@ -0,0 +1,72 @@ +# Run gno transpile with -gobuild flag + +gno transpile -gobuild -v . + +! stdout .+ +cmp stderr stderr.golden + +# The test file will be excluded from transpilation unless we pass it explicitly. +cmp main.gno.gen.go main.gno.gen.go.golden +! exists .main_test.gno.gen_test.go +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 + +! stdout .+ +cmpenv stderr stderr2.golden + +cmp main.gno.gen.go main.gno.gen.go.golden +! exists .main_test.gno.gen_test.go +cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden + +-- stderr.golden -- +. +sub +. [build] +sub [build] +-- stderr2.golden -- +$WORK +$WORK/sub +$WORK [build] +$WORK/sub [build] +-- main.gno -- +package main + +func main() { + var x = 1 + _=x +} +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + var x = 1 + _ = x +} +-- main_test.gno -- +package main + +import ( + "testing" + "badimport" +) + +func TestMain(t *testing.T) { + badimport.DoesNotExist() +} +-- sub/sub.gno -- +package sub +-- sub/sub.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line sub.gno:1:1 +package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar new file mode 100644 index 00000000000..b1a63890f46 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar @@ -0,0 +1,41 @@ +# Run gno transpile with valid gno files, using the -output flag. + +gno transpile -v -output directory/hello/ . +! stdout .+ +cmp stderr stderr1.golden + +exists directory/hello/main.gno.gen.go +! exists main.gno.gen.go +rm directory + +# Try running using the absolute path to the directory. +gno transpile -v -output directory/hello $WORK +! stdout .+ +cmpenv stderr stderr2.golden + +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go +rm directory + +# Try running in subdirectory, using a "relative non-local path." (ie. has "../") +mkdir subdir +cd subdir +gno transpile -v -output hello .. +! stdout .+ +cmpenv stderr ../stderr3.golden + +exists hello$WORK/main.gno.gen.go +! exists main.gno.gen.go + +-- stderr1.golden -- +. +-- stderr2.golden -- +$WORK +-- stderr3.golden -- +.. +-- main.gno -- +package main + +func main() { + println("hello") +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar new file mode 100644 index 00000000000..3540e865f3e --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar @@ -0,0 +1,44 @@ +# Run gno transpile with valid gno files, using the -output and -gobuild flags together. + +gno transpile -v -output directory/hello/ -gobuild . +! stdout .+ +cmp stderr stderr1.golden + +exists directory/hello/main.gno.gen.go +! exists main.gno.gen.go +rm directory + +# Try running using the absolute path to the directory. +gno transpile -v -output directory/hello -gobuild $WORK +! stdout .+ +cmpenv stderr stderr2.golden + +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go +rm directory + +# Try running in subdirectory, using a "relative non-local path." (ie. has "../") +mkdir subdir +cd subdir +gno transpile -v -output hello -gobuild .. +! stdout .+ +cmpenv stderr ../stderr3.golden + +exists hello$WORK/main.gno.gen.go +! exists main.gno.gen.go + +-- stderr1.golden -- +. +directory/hello [build] +-- stderr2.golden -- +$WORK +directory/hello$WORK [build] +-- stderr3.golden -- +.. +hello$WORK [build] +-- main.gno -- +package main + +func main() { + println("hello") +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar new file mode 100644 index 00000000000..86cc6f12f7a --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar @@ -0,0 +1,65 @@ +# Run gno transpile with an individual file. + +# Running transpile on the current directory should only precompile +# main.gno. +gno transpile -v . + +! stdout .+ +stderr ^\.$ + +exists main.gno.gen.go +! exists .hello_test.gno.gen_test.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 + +! stdout .+ +cmp stderr transpile-files-stderr.golden + +cmp main.gno.gen.go main.gno.gen.go.golden +cmp .hello_test.gno.gen_test.go .hello_test.gno.gen_test.go.golden + +-- transpile-files-stderr.golden -- +main.gno +hello_test.gno +-- main.gno -- +package main + +func main() { + println("hello") +} + +-- hello_test.gno -- +package main + +import "std" + +func hello() { + std.AssertOriginCall() +} + +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + println("hello") +} +-- .hello_test.gno.gen_test.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno && test + +//line hello_test.gno:1:1 +package main + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func hello() { + std.AssertOriginCall(nil) +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar new file mode 100644 index 00000000000..a765ab5093b --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar @@ -0,0 +1,94 @@ +# Run gno transpile with dependencies +env GNOROOT=$WORK + +gno transpile -v ./examples + +! stdout .+ +cmpenv stderr stderr.golden + +! exists examples/gno.land/r/question/question.gno.gen.go +cmp examples/gno.land/r/answer/answer.gno.gen.go examples/gno.land/r/answer/answer.gno.gen.go.golden +cmp examples/gno.land/r/answer/anti_answer.gno.gen.go examples/gno.land/r/answer/anti_answer.gno.gen.go.golden +cmp gnovm/stdlibs/math/math.gno.gen.go gnovm/stdlibs/math/math.gno.gen.go.golden + +-- stderr.golden -- +examples/gno.land/r/answer +$WORK/gnovm/stdlibs/math +examples/gno.land/r/question (skipped, gno.mod marks module as draft) +-- examples/gno.land/r/question/gno.mod -- +// Draft + +module gno.land/r/question + +-- examples/gno.land/r/question/question.gno -- +package question + +func Question() string { + return "What is the answer to Life, The Universe and Everything?" + invalid syntax +} + +-- examples/gno.land/r/answer/answer.gno -- +package answer + +import "math" + +func Answer() int { + return math.Sqrt(1764) +} + +-- examples/gno.land/r/answer/answer.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line answer.gno:1:1 +package answer + +import "github.com/gnolang/gno/gnovm/stdlibs/math" + +func Answer() int { + return math.Sqrt(1764) +} +-- examples/gno.land/r/answer/anti_answer.gno -- +package answer + +import "math" + +func AntiAnswer() int { + return -math.Sqrt(1764) +} +-- examples/gno.land/r/answer/anti_answer.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line anti_answer.gno:1:1 +package answer + +import "github.com/gnolang/gno/gnovm/stdlibs/math" + +func AntiAnswer() int { + return -math.Sqrt(1764) +} +-- examples/gno.land/r/answer/gno.mod -- +module gno.land/r/answer + +-- gnovm/stdlibs/math/math.gno -- +package math + +func Sqrt(i int) int { + return 42 +} + +-- gnovm/stdlibs/math/math.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line math.gno:1:1 +package math + +func Sqrt(i int) int { + return 42 +} diff --git a/gnovm/cmd/gno/testdata_test.go b/gnovm/cmd/gno/testdata_test.go new file mode 100644 index 00000000000..15bc8d96e26 --- /dev/null +++ b/gnovm/cmd/gno/testdata_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/gnolang/gno/gnovm/pkg/integration" + "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/require" +) + +func Test_Scripts(t *testing.T) { + testdata, err := filepath.Abs("testdata") + require.NoError(t, err) + + testdirs, err := os.ReadDir(testdata) + require.NoError(t, err) + + for _, dir := range testdirs { + if !dir.IsDir() { + continue + } + + name := dir.Name() + t.Logf("testing: %s", name) + t.Run(name, func(t *testing.T) { + updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) + p := testscript.Params{ + UpdateScripts: updateScripts, + Dir: filepath.Join(testdata, name), + } + + if coverdir, ok := integration.ResolveCoverageDir(); ok { + err := integration.SetupTestscriptsCoverage(&p, coverdir) + require.NoError(t, err) + } + + err := integration.SetupGno(&p, t.TempDir()) + require.NoError(t, err) + + testscript.Run(t, p) + }) + } +} diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 3469304bea2..1e3081ca2b0 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -5,53 +5,61 @@ import ( "errors" "flag" "fmt" + "go/ast" "go/scanner" - "log" + "go/token" "os" + "os/exec" "path/filepath" + "regexp" + "slices" + "strconv" + "strings" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) -type importPath string - type transpileCfg struct { verbose bool - skipFmt bool + rootDir string skipImports bool gobuild bool goBinary string - gofmtBinary string output string } type transpileOptions struct { cfg *transpileCfg + // CLI output + io commands.IO // transpiled is the set of packages already // transpiled from .gno to .go. - transpiled map[importPath]struct{} -} - -var defaultTranspileCfg = &transpileCfg{ - verbose: false, - goBinary: "go", + transpiled map[string]struct{} + // skipped packages (gno mod marks them as draft) + skipped []string } -func newTranspileOptions(cfg *transpileCfg) *transpileOptions { - return &transpileOptions{cfg, map[importPath]struct{}{}} +func newTranspileOptions(cfg *transpileCfg, io commands.IO) *transpileOptions { + return &transpileOptions{ + cfg: cfg, + io: io, + transpiled: map[string]struct{}{}, + } } func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *transpileOptions) isTranspiled(pkg importPath) bool { +func (p *transpileOptions) isTranspiled(pkg string) bool { _, transpiled := p.transpiled[pkg] return transpiled } -func (p *transpileOptions) markAsTranspiled(pkg importPath) { +func (p *transpileOptions) markAsTranspiled(pkg string) { p.transpiled[pkg] = struct{}{} } @@ -79,11 +87,11 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { "verbose output when running", ) - fs.BoolVar( - &c.skipFmt, - "skip-fmt", - false, - "do not check syntax of generated .go files", + fs.StringVar( + &c.rootDir, + "root-dir", + "", + "clone location of github.com/gnolang/gno (gno tries to guess it)", ) fs.BoolVar( @@ -107,13 +115,6 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { "go binary to use for building", ) - fs.StringVar( - &c.gofmtBinary, - "go-fmt-binary", - "gofmt", - "gofmt binary to use for syntax checking", - ) - fs.StringVar( &c.output, "output", @@ -127,33 +128,54 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return flag.ErrHelp } - // transpile .gno files. - paths, err := gnoFilesFromArgs(args) + // guess cfg.RootDir + if cfg.rootDir == "" { + cfg.rootDir = gnoenv.RootDir() + } + + // transpile .gno packages and files. + paths, err := gnoFilesFromArgsRecursively(args) if err != nil { return fmt.Errorf("list paths: %w", err) } - opts := newTranspileOptions(cfg) + opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList - for _, filepath := range paths { - if err := transpileFile(filepath, opts); err != nil { + for _, path := range paths { + st, err := os.Stat(path) + if err != nil { + return err + } + if st.IsDir() { + err = transpilePkg(path, opts) + } else { + if opts.cfg.verbose { + io.ErrPrintln(filepath.Clean(path)) + } + + err = transpileFile(path, opts) + } + if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: transpile: %w", filepath, err) + return fmt.Errorf("%s: transpile: %w", path, err) } errlist = append(errlist, fileErrlist...) } } if errlist.Len() == 0 && cfg.gobuild { - paths, err := gnoPackagesFromArgs(args) - if err != nil { - return fmt.Errorf("list packages: %w", err) - } - for _, pkgPath := range paths { - err := goBuildFileOrPkg(pkgPath, cfg) + if slices.Contains(opts.skipped, pkgPath) { + continue + } + if cfg.output != "." { + if pkgPath, err = ResolvePath(cfg.output, pkgPath); err != nil { + return fmt.Errorf("resolve output path: %w", err) + } + } + err := goBuildFileOrPkg(io, pkgPath, cfg) if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { @@ -174,19 +196,41 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return nil } -func transpilePkg(pkgPath importPath, opts *transpileOptions) error { - if opts.isTranspiled(pkgPath) { +// transpilePkg transpiles all non-test files at the given location. +// Additionally, it checks the gno.mod in said location, and skips it if it is +// a draft module +func transpilePkg(dirPath string, opts *transpileOptions) error { + if opts.isTranspiled(dirPath) { return nil } - opts.markAsTranspiled(pkgPath) + opts.markAsTranspiled(dirPath) - files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) + gmod, err := gnomod.ParseAt(dirPath) + if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) { + return err + } + if err == nil && gmod.Draft { + if opts.cfg.verbose { + opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", filepath.Clean(dirPath)) + } + opts.skipped = append(opts.skipped, dirPath) + return nil + } + + // XXX(morgan): Currently avoiding test files as they contain imports like "fmt". + // The transpiler doesn't currently support "test stdlibs", and even if it + // did all packages like "fmt" would have to exist as standard libraries to work. + // Easier to skip for now. + files, err := listNonTestFiles(dirPath) if err != nil { - log.Fatal(err) + return err } + if opts.cfg.verbose { + opts.io.ErrPrintln(filepath.Clean(dirPath)) + } for _, file := range files { - if err = transpileFile(file, opts); err != nil { + if err := transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -196,14 +240,6 @@ func transpilePkg(pkgPath importPath, opts *transpileOptions) error { func transpileFile(srcPath string, opts *transpileOptions) error { flags := opts.getFlags() - gofmt := flags.gofmtBinary - if gofmt == "" { - gofmt = "gofmt" - } - - if flags.verbose { - fmt.Fprintf(os.Stderr, "%s\n", srcPath) - } // parse .gno. source, err := os.ReadFile(srcPath) @@ -212,7 +248,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { } // compute attributes based on filename. - targetFilename, tags := transpiler.GetTranspileFilenameAndTags(srcPath) + targetFilename, tags := transpiler.TranspiledFilenameAndTags(srcPath) // preprocess. transpileRes, err := transpiler.Transpile(string(source), tags, srcPath) @@ -223,7 +259,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { // resolve target path var targetPath string if flags.output != "." { - path, err := ResolvePath(flags.output, importPath(filepath.Dir(srcPath))) + path, err := ResolvePath(flags.output, filepath.Dir(srcPath)) if err != nil { return fmt.Errorf("resolve output path: %w", err) } @@ -238,32 +274,139 @@ func transpileFile(srcPath string, opts *transpileOptions) error { return fmt.Errorf("write .go file: %w", err) } - // check .go fmt, if `SkipFmt` sets to false. - if !flags.skipFmt { - err = transpiler.TranspileVerifyFile(targetPath, gofmt) + // transpile imported packages, if `SkipImports` sets to false + if !flags.skipImports && + !strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") { + dirPaths, err := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) if err != nil { - return fmt.Errorf("check .go file: %w", err) + return err } - } - - // transpile imported packages, if `SkipImports` sets to false - if !flags.skipImports { - importPaths := getPathsFromImportSpec(transpileRes.Imports) - for _, path := range importPaths { - transpilePkg(path, opts) + for _, path := range dirPaths { + if err := transpilePkg(path, opts); err != nil { + return err + } } } return nil } -func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { +func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error { verbose := cfg.verbose goBinary := cfg.goBinary if verbose { - fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg) + io.ErrPrintfln("%s [build]", filepath.Clean(fileOrPkg)) + } + + return buildTranspiledPackage(fileOrPkg, goBinary) +} + +// getPathsFromImportSpec returns the directory paths where the code for each +// importSpec is stored (assuming they start with [transpiler.ImportPrefix]). +func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string, err error) { + for _, i := range importSpec { + path, err := strconv.Unquote(i.Path.Value) + if err != nil { + return nil, err + } + if strings.HasPrefix(path, transpiler.ImportPrefix) { + res := strings.TrimPrefix(path, transpiler.ImportPrefix) + + dirs = append(dirs, rootDir+filepath.FromSlash(res)) + } + } + return +} + +// buildTranspiledPackage tries to run `go build` against the transpiled .go files. +// +// This method is the most efficient to detect errors but requires that +// all the import are valid and available. +func buildTranspiledPackage(fileOrPkg, goBinary string) error { + // TODO: use cmd/compile instead of exec? + // TODO: find the nearest go.mod file, chdir in the same folder, trim prefix? + // TODO: temporarily create an in-memory go.mod or disable go modules for gno? + // TODO: ignore .go files that were not generated from gno? + + info, err := os.Stat(fileOrPkg) + if err != nil { + return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) + } + var ( + target string + chdir string + ) + if !info.IsDir() { + dstFilename, _ := transpiler.TranspiledFilenameAndTags(fileOrPkg) + // Makes clear to go compiler that this is a relative path, + // rather than a path to a package/module. + // can't use filepath.Join as it cleans its results. + target = filepath.Dir(fileOrPkg) + string(filepath.Separator) + dstFilename + } else { + // Go does not allow building packages using absolute paths, and requires + // relative paths to always be prefixed with `./` (because the argument + // go expects are import paths, not directories). + // To circumvent this, we use the -C flag to chdir into the right + // directory, then run `go build .` + chdir = fileOrPkg + target = "." + } + + // pre-alloc max 5 args + args := append(make([]string, 0, 5), "build") + if chdir != "" { + args = append(args, "-C", chdir) + } + args = append(args, "-tags=gno", target) + cmd := exec.Command(goBinary, args...) + out, err := cmd.CombinedOutput() + if errors.As(err, new(*exec.ExitError)) { + // there was a non-zero exit code; parse the go build errors + return parseGoBuildErrors(string(out)) + } + // other kinds of errors; return + return err +} + +var ( + reGoBuildError = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) + reGoBuildComment = regexp.MustCompile(`(?m)^#.*$`) +) + +// parseGoBuildErrors returns a scanner.ErrorList filled with all errors found +// in out, which is supposed to be the output of the `go build` command. +// +// TODO(tb): update when `go build -json` is released to replace regexp usage. +// See https://github.com/golang/go/issues/62067 +func parseGoBuildErrors(out string) error { + var errList scanner.ErrorList + matches := reGoBuildError.FindAllStringSubmatch(out, -1) + for _, match := range matches { + filename := match[1] + line, err := strconv.Atoi(match[2]) + if err != nil { + return fmt.Errorf("parse line go build error %s: %w", match, err) + } + + column, err := strconv.Atoi(match[3]) + if err != nil { + return fmt.Errorf("parse column go build error %s: %w", match, err) + } + msg := match[4] + errList.Add(token.Position{ + Filename: filename, + Line: line, + Column: column, + }, msg) + } + + replaced := reGoBuildError.ReplaceAllLiteralString(out, "") + replaced = reGoBuildComment.ReplaceAllString(replaced, "") + replaced = strings.TrimSpace(replaced) + if replaced != "" { + errList.Add(token.Position{}, "Additional go build errors:\n"+replaced) } - return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) + return errList.Err() } diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 2770026a01a..827c09e23f1 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -1,9 +1,13 @@ package main import ( + "go/scanner" + "go/token" + "strconv" "testing" "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gnolang/gno/gnovm/pkg/integration" @@ -24,3 +28,79 @@ func Test_ScriptsTranspile(t *testing.T) { testscript.Run(t, p) } + +func Test_parseGoBuildErrors(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + output string + expectedError error + expectedErrorIs error + }{ + { + name: "empty output", + output: "", + expectedError: nil, + }, + { + name: "random output", + output: "xxx", + expectedError: scanner.ErrorList{ + &scanner.Error{ + Msg: "Additional go build errors:\nxxx", + }, + }, + }, + { + name: "some errors", + output: `xxx +main.gno:6:2: nasty error +pkg/file.gno:60:20: ugly error`, + expectedError: scanner.ErrorList{ + &scanner.Error{ + Pos: token.Position{ + Filename: "main.gno", + Line: 6, + Column: 2, + }, + Msg: "nasty error", + }, + &scanner.Error{ + Pos: token.Position{ + Filename: "pkg/file.gno", + Line: 60, + Column: 20, + }, + Msg: "ugly error", + }, + &scanner.Error{ + Msg: "Additional go build errors:\nxxx", + }, + }, + }, + { + name: "line parse error", + output: `main.gno:9000000000000000000000000000000000000000000000000000:11: error`, + expectedErrorIs: strconv.ErrRange, + }, + { + name: "column parse error", + output: `main.gno:1:9000000000000000000000000000000000000000000000000000: error`, + expectedErrorIs: strconv.ErrRange, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := parseGoBuildErrors(tt.output) + if eis := tt.expectedErrorIs; eis != nil { + assert.ErrorIs(t, err, eis) + } else { + assert.Equal(t, tt.expectedError, err) + } + }) + } +} diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 30d8f808d04..90aedd5d27a 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "go/ast" "io" "io/fs" "os" @@ -10,9 +9,6 @@ import ( "regexp" "strings" "time" - - "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/transpiler" ) func isGnoFile(f fs.DirEntry) bool { @@ -25,83 +21,128 @@ func isFileExist(path string) bool { return err == nil } -func gnoFilesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) +func gnoFilesFromArgsRecursively(args []string) ([]string, error) { + var paths []string + + for _, argPath := range args { + info, err := os.Stat(argPath) if err != nil { return nil, fmt.Errorf("invalid file or package path: %w", err) } + if !info.IsDir() { - curpath := arg - paths = append(paths, curpath) - } else { - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - - if !isGnoFile(f) { - return nil // skip - } - paths = append(paths, curpath) - return nil - }) - if err != nil { - return nil, err + if isGnoFile(fs.FileInfoToDirEntry(info)) { + paths = append(paths, ensurePathPrefix(argPath)) } + + continue + } + + // Gather package paths from the directory + err = walkDirForGnoFiles(argPath, func(path string) { + paths = append(paths, ensurePathPrefix(path)) + }) + if err != nil { + return nil, fmt.Errorf("unable to walk dir: %w", err) } } + return paths, nil } -func gnoPackagesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) +func gnoFilesFromArgs(args []string) ([]string, error) { + var paths []string + + for _, argPath := range args { + info, err := os.Stat(argPath) if err != nil { return nil, fmt.Errorf("invalid file or package path: %w", err) } + if !info.IsDir() { - paths = append(paths, arg) - } else { - // if the passed arg is a dir, then we'll recursively walk the dir - // and look for directories containing at least one .gno file. - - visited := map[string]bool{} // used to run the builder only once per folder. - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - if f.IsDir() { - return nil // skip - } - if !isGnoFile(f) { - return nil // skip - } - - parentDir := filepath.Dir(curpath) - if _, found := visited[parentDir]; found { - return nil - } - visited[parentDir] = true - - pkg := parentDir - if !filepath.IsAbs(parentDir) { - // cannot use path.Join or filepath.Join, because we need - // to ensure that ./ is the prefix to pass to go build. - // if not absolute. - pkg = "./" + parentDir - } - - paths = append(paths, pkg) - return nil - }) - if err != nil { - return nil, err + if isGnoFile(fs.FileInfoToDirEntry(info)) { + paths = append(paths, ensurePathPrefix(argPath)) + } + continue + } + + files, err := os.ReadDir(argPath) + if err != nil { + return nil, err + } + for _, f := range files { + if isGnoFile(f) { + path := filepath.Join(argPath, f.Name()) + paths = append(paths, ensurePathPrefix(path)) } } } + + return paths, nil +} + +func ensurePathPrefix(path string) string { + if filepath.IsAbs(path) { + return path + } + + // cannot use path.Join or filepath.Join, because we need + // to ensure that ./ is the prefix to pass to go build. + // if not absolute. + return "." + string(filepath.Separator) + path +} + +func walkDirForGnoFiles(root string, addPath func(path string)) error { + visited := make(map[string]struct{}) + + walkFn := func(currPath string, f fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("%s: walk dir: %w", root, err) + } + + if f.IsDir() || !isGnoFile(f) { + return nil + } + + parentDir := filepath.Dir(currPath) + if _, found := visited[parentDir]; found { + return nil + } + + visited[parentDir] = struct{}{} + + addPath(parentDir) + + return nil + } + + return filepath.WalkDir(root, walkFn) +} + +func gnoPackagesFromArgsRecursively(args []string) ([]string, error) { + var paths []string + + for _, argPath := range args { + info, err := os.Stat(argPath) + if err != nil { + return nil, fmt.Errorf("invalid file or package path: %w", err) + } + + if !info.IsDir() { + paths = append(paths, ensurePathPrefix(argPath)) + + continue + } + + // Gather package paths from the directory + err = walkDirForGnoFiles(argPath, func(path string) { + paths = append(paths, ensurePathPrefix(path)) + }) + if err != nil { + return nil, fmt.Errorf("unable to walk dir: %w", err) + } + } + return paths, nil } @@ -192,42 +233,27 @@ func fmtDuration(d time.Duration) string { return fmt.Sprintf("%.2fs", d.Seconds()) } -// makeTestGoMod creates the temporary go.mod for test -func makeTestGoMod(path string, packageName string, goversion string) error { - content := fmt.Sprintf("module %s\n\ngo %s\n", packageName, goversion) - return os.WriteFile(path, []byte(content), 0o644) -} - -// getPathsFromImportSpec derive and returns ImportPaths -// without ImportPrefix from *ast.ImportSpec -func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importPath) { - for _, i := range importSpec { - path := i.Path.Value[1 : len(i.Path.Value)-1] // trim leading and trailing `"` - if strings.HasPrefix(path, transpiler.ImportPrefix) { - res := strings.TrimPrefix(path, transpiler.ImportPrefix) - importPaths = append(importPaths, importPath("."+res)) - } +// ResolvePath determines the path where to place output files. +// output is the output directory provided by the user. +// dstPath is the desired output path by the gno program. +// +// If dstPath is relative non-local path (ie. contains ../), the dstPath will +// be made absolute and joined with output. +// +// Otherwise, the result is simply filepath.Join(output, dstPath). +// +// See related test for examples. +func ResolvePath(output, dstPath string) (string, error) { + if filepath.IsAbs(dstPath) || + filepath.IsLocal(dstPath) { + return filepath.Join(output, dstPath), nil } - return -} - -// ResolvePath joins the output dir with relative pkg path -// e.g -// Output Dir: Temp/gno-transpile -// Pkg Path: ../example/gno.land/p/pkg -// Returns -> Temp/gno-transpile/example/gno.land/p/pkg -func ResolvePath(output string, path importPath) (string, error) { - absOutput, err := filepath.Abs(output) + // Make dstPath absolute and join it with output. + absDst, err := filepath.Abs(dstPath) if err != nil { return "", err } - absPkgPath, err := filepath.Abs(string(path)) - if err != nil { - return "", err - } - pkgPath := strings.TrimPrefix(absPkgPath, gnoenv.RootDir()) - - return filepath.Join(absOutput, pkgPath), nil + return filepath.Join(output, absDst), nil } // WriteDirFile write file to the path and also create diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go index 9e9659bfe4f..a92c924e272 100644 --- a/gnovm/cmd/gno/util_test.go +++ b/gnovm/cmd/gno/util_test.go @@ -295,3 +295,50 @@ func createGnoPackages(t *testing.T, tmpDir string) { } } } + +func TestResolvePath(t *testing.T) { + t.Parallel() + + if os.PathSeparator != '/' { + t.Skip("ResolvePath test is only written of UNIX-like filesystems") + } + wd, err := os.Getwd() + require.NoError(t, err) + tt := []struct { + output string + dstPath string + result string + }{ + { + "transpile-result", + "./examples/test/test1.gno.gen.go", + "transpile-result/examples/test/test1.gno.gen.go", + }, + { + "/transpile-result", + "./examples/test/test1.gno.gen.go", + "/transpile-result/examples/test/test1.gno.gen.go", + }, + { + "/transpile-result", + "/home/gno/examples/test/test1.gno.gen.go", + "/transpile-result/home/gno/examples/test/test1.gno.gen.go", + }, + { + "result", + "../hello", + filepath.Join("result", filepath.Join(wd, "../hello")), + }, + } + + for _, tc := range tt { + res, err := ResolvePath(tc.output, tc.dstPath) + // ResolvePath should error only in case we can't get the abs path; + // so never in normal conditions. + require.NoError(t, err) + assert.Equal(t, + tc.result, res, + "unexpected result of ResolvePath(%q, %q)", tc.output, tc.dstPath, + ) + } +} diff --git a/gnovm/gno.proto b/gnovm/gno.proto index 481e89e22de..5f53c363b73 100644 --- a/gnovm/gno.proto +++ b/gnovm/gno.proto @@ -140,7 +140,7 @@ message Location { string PkgPath = 1; string File = 2; sint64 Line = 3; - sint64 Nonce = 4; + sint64 Column = 4; } message Attributes { @@ -600,4 +600,4 @@ message tupleType { message RefType { string ID = 1; -} \ No newline at end of file +} diff --git a/gnovm/pkg/gnofmt/package.go b/gnovm/pkg/gnofmt/package.go new file mode 100644 index 00000000000..c576bd8ee78 --- /dev/null +++ b/gnovm/pkg/gnofmt/package.go @@ -0,0 +1,156 @@ +package gnofmt + +import ( + "fmt" + "go/parser" + "go/token" + "io" + "os" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gnovm/pkg/gnomod" +) + +type Package interface { + // Should return the package path + Path() string + // Should return the name of the package as defined at the top level of each file + Name() string + // Should return all gno filenames inside the package + Files() []string + // Should return a content reader for the given filename within the package + Read(filename string) (io.ReadCloser, error) +} + +type PackageReadWalkFunc func(filename string, r io.Reader, err error) error + +func ReadWalkPackage(pkg Package, fn PackageReadWalkFunc) error { + for _, filename := range pkg.Files() { + if !isGnoFile(filename) { + return nil + } + + r, err := pkg.Read(filename) + fnErr := fn(filename, r, err) + r.Close() + if fnErr != nil { + return fnErr + } + } + + return nil +} + +type fsPackage struct { + path string + name string + dir string + files []string // filenames +} + +// ParsePackage parses package from the given directory. +// It will return a nil package if no gno files are found. +// If a gno.mod is found, it will be used to determine the pkg path. +// If root is specified, it will be trimmed from the actual given dir to create the pkgpath if no gno.mod is found. +func ParsePackage(fset *token.FileSet, root string, dir string) (Package, error) { + files, err := os.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("unable to read dir %q: %w", dir, err) + } + + var pkgname string + + gnofiles := []string{} + for _, file := range files { + name := file.Name() + if !isGnoFile(name) { + continue + } + + // Ignore package name from test files + if isTestFile(name) { + gnofiles = append(gnofiles, name) + continue + } + + filename := filepath.Join(dir, name) + f, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly) + if err != nil { + return nil, fmt.Errorf("unable to parse file %q: %w", filename, err) + } + + if pkgname != "" && pkgname != f.Name.Name { + return nil, fmt.Errorf("conflict package name between %q and %q", pkgname, f.Name.Name) + } + + pkgname = f.Name.Name + gnofiles = append(gnofiles, name) + } + + if len(gnofiles) == 0 { + return nil, nil // Not a package + } + + var pkgpath string + + // Check for a gno.mod, in which case it will define the module path + gnoModPath := filepath.Join(dir, "gno.mod") + data, err := os.ReadFile(gnoModPath) + switch { + case os.IsNotExist(err): + if len(root) > 0 { + // Fallback on dir path trimmed from the root + pkgpath = strings.TrimPrefix(dir, filepath.Clean(root)) + pkgpath = strings.TrimPrefix(pkgpath, "/") + } + + case err == nil: + gnoMod, err := gnomod.Parse(gnoModPath, data) + if err != nil { + return nil, fmt.Errorf("unable to parse gnomod %q: %w", gnoModPath, err) + } + + gnoMod.Sanitize() + if err := gnoMod.Validate(); err != nil { + return nil, fmt.Errorf("unable to validate gnomod %q: %w", gnoModPath, err) + } + + pkgpath = gnoMod.Module.Mod.Path + default: + return nil, fmt.Errorf("unable to read %q: %w", gnoModPath, err) + } + + return &fsPackage{ + path: pkgpath, + files: gnofiles, + dir: dir, + name: pkgname, + }, nil +} + +func (p *fsPackage) Path() string { + return p.path +} + +func (p *fsPackage) Name() string { + return p.name +} + +func (p *fsPackage) Files() []string { + return p.files +} + +func (p *fsPackage) Read(filename string) (io.ReadCloser, error) { + if !isGnoFile(filename) { + return nil, fmt.Errorf("invalid gno file %q", filename) + } + + path := filepath.Join(p.dir, filename) + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open %q: %w", path, err) + } + + return file, nil +} diff --git a/gnovm/pkg/gnofmt/processes_test.go b/gnovm/pkg/gnofmt/processes_test.go new file mode 100644 index 00000000000..6c56012c3ad --- /dev/null +++ b/gnovm/pkg/gnofmt/processes_test.go @@ -0,0 +1,96 @@ +// For convenient purposes, more tests have been created using `testscripts` format and are located in `gnovm/cmd/testdata/gno_fmt/` folder + +package gnofmt + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormatImportFromSource(t *testing.T) { + t.Parallel() + + mockResolver := newMockResolver() + + mp := newMockedPackage("example.com/mypkg", "mypkg") + pkgcontent := `package mypkg + +func MyFunc(str string) string{ + return "Hello: "+str +}` + mp.AddFile("my.gno", []byte(pkgcontent)) + mockResolver.AddPackage(mp) + + sourceCode := `package main + +func main() { + str := "hello, world" + mypkg.MyFunc(str) +}` + + // Add packages to the MockResolver + processor := NewProcessor(mockResolver) + formatted, err := processor.FormatImportFromSource("main.go", sourceCode) + require.NoError(t, err) + + expectedOutput := `package main + +import "example.com/mypkg" + +func main() { + str := "hello, world" + mypkg.MyFunc(str) +} +` + + require.Equal(t, expectedOutput, string(formatted)) +} + +func TestFormatImportFromFile(t *testing.T) { + t.Parallel() + + mockResolver := newMockResolver() + + // Add packages to the MockResolver + mp := newMockedPackage("example.com/mypkg", "mypkg") + pkgcontent := `package mypkg + +func MyFunc(str string) string{ + return "Hello: "+str +}` + mp.AddFile("my.gno", []byte(pkgcontent)) + mockResolver.AddPackage(mp) + + processor := NewProcessor(mockResolver) + sourceFile := "main.gno" + sourceCode := `package main + +func main() { + str := "hello, world" + println(mypkg.MyFunc(str)) +}` + + expectedOutput := `package main + +import "example.com/mypkg" + +func main() { + str := "hello, world" + println(mypkg.MyFunc(str)) +} +` + // Create a temporary directory and file + dir := t.TempDir() + filePath := filepath.Join(dir, sourceFile) + + err := os.WriteFile(filePath, []byte(sourceCode), 0o644) + require.NoError(t, err) + + formatted, err := processor.FormatFile(filePath) + require.NoError(t, err) + + require.Equal(t, expectedOutput, string(formatted)) +} diff --git a/gnovm/pkg/gnofmt/processor.go b/gnovm/pkg/gnofmt/processor.go new file mode 100644 index 00000000000..c6484fe6784 --- /dev/null +++ b/gnovm/pkg/gnofmt/processor.go @@ -0,0 +1,334 @@ +package gnofmt + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "io" + "path/filepath" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/imports" +) + +const tabWidth = 8 + +type parsedPackage struct { + error error + files map[string]*ast.File + decls map[*ast.Object]ast.Decl +} + +type Processor struct { + resolver Resolver + fset *token.FileSet + + // cache package parsing in `FormatFile` call + pkgdirCache map[string]Package // dir -> pkg cache package dir + + // cache for global parsed package + parsedPackage map[string]*parsedPackage // pkgdir -> parsed package +} + +func NewProcessor(r Resolver) *Processor { + return &Processor{ + resolver: r, + fset: token.NewFileSet(), + pkgdirCache: make(map[string]Package), + parsedPackage: make(map[string]*parsedPackage), + } +} + +// FormatImportFromSource parse and format the source from src. The type of the argument +// for the src parameter must be string, []byte, or [io.Reader]. +func (p *Processor) FormatImportFromSource(filename string, src any) ([]byte, error) { + // Parse the source file + nodefile, err := p.parseFile(filename, src) + if err != nil { + return nil, fmt.Errorf("unable to parse source: %w", err) + } + + // Collect top level declarations within the source + pkgDecls := make(map[*ast.Object]ast.Decl) + collectTopDeclaration(nodefile, pkgDecls) + + // Process and format the parsed node. + return p.processAndFormat(nodefile, filename, pkgDecls) +} + +// FormatFile processes a single Gno file from the given Package and filename. +func (p *Processor) FormatPackageFile(pkg Package, filename string) ([]byte, error) { + // Process package files. + pkgc := p.processPackageFiles(pkg.Path(), pkg) + if pkgc.error != nil { + return nil, fmt.Errorf("unable to process package: %w", pkgc.error) + } + + // Retrieve the nodefile for the file. + nodefile, ok := pkgc.files[filename] + if !ok { + return nil, fmt.Errorf("not a valid gno file: %s", filename) + } + + return p.processAndFormat(nodefile, filename, pkgc.decls) +} + +// FormatFile processes a single Gno file from the given file path. +func (p *Processor) FormatFile(file string) ([]byte, error) { + filename := filepath.Base(file) + dir := filepath.Dir(file) + + pkg, ok := p.pkgdirCache[dir] + if !ok { + var err error + pkg, err = ParsePackage(p.fset, "", dir) + if err != nil { + return nil, fmt.Errorf("unable to parse package %q: %w", dir, err) + } + p.pkgdirCache[dir] = pkg + } + + if pkg == nil { + fmt.Printf("-> parsing file: %q, %q\n", file, filename) + // Fallback on src + return p.FormatImportFromSource(filename, nil) + } + + path := pkg.Path() + if path == "" { + // Use dir as package path + path = dir + } + + // Process package files. + pkgc := p.processPackageFiles(dir, pkg) + if pkgc.error != nil { + return nil, fmt.Errorf("unable to process package: %w", pkgc.error) + } + + // Retrieve the nodefile for the file. + nodefile, ok := pkgc.files[filename] + if !ok { + return nil, fmt.Errorf("not a valid gno file: %s", filename) + } + + return p.processAndFormat(nodefile, filename, pkgc.decls) +} + +func (p *Processor) parseFile(path string, src any) (file *ast.File, err error) { + // Parse the source file + file, err = parser.ParseFile(p.fset, path, src, parser.ParseComments|parser.AllErrors) + if err != nil { + return nil, fmt.Errorf("unable to parse file %q: %w", path, err) + } + + return file, nil +} + +// Helper function to process and format a parsed AST node. +func (p *Processor) processAndFormat(file *ast.File, filename string, topDecls map[*ast.Object]ast.Decl) ([]byte, error) { + // Collect unresolved + unresolved := collectUnresolved(file, topDecls) + + // Cleanup and remove previous unused import + p.cleanupPreviousImports(file, topDecls, unresolved) + + // Resolve unresolved declarations + p.resolve(file, unresolved) + + // Process output + var buf bytes.Buffer + if err := printer.Fprint(&buf, p.fset, file); err != nil { + return nil, fmt.Errorf("unable to format file: %w", err) + } + + // Use go/imports for formating and sort imports. + ret, err := imports.Process(filename, buf.Bytes(), &imports.Options{ + TabWidth: tabWidth, + Comments: true, + TabIndent: true, + FormatOnly: true, + }) + if err != nil { + return nil, fmt.Errorf("unable to format import: %w", err) + } + + return ret, nil +} + +// processPackageFiles processes Gno package files and collects top-level declarations. +func (p *Processor) processPackageFiles(path string, pkg Package) *parsedPackage { + pkgc, ok := p.parsedPackage[path] + if ok { + return pkgc + } + + pkgc = &parsedPackage{ + decls: make(map[*ast.Object]ast.Decl), + files: map[string]*ast.File{}, + } + pkgc.error = ReadWalkPackage(pkg, func(filename string, r io.Reader, err error) error { + if err != nil { + return fmt.Errorf("unable to read %q: %w", filename, err) + } + + file, err := p.parseFile(filename, r) + if err != nil { + return err + } + + collectTopDeclaration(file, pkgc.decls) + pkgc.files[filename] = file + return nil + }) + p.parsedPackage[path] = pkgc + + return pkgc +} + +// collectTopDeclaration collects top-level declarations from a single file. +func collectTopDeclaration(file *ast.File, topDecls map[*ast.Object]ast.Decl) { + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + topDecls[s.Name.Obj] = d + case *ast.ValueSpec: + for _, name := range s.Names { + topDecls[name.Obj] = d + } + } + } + case *ast.FuncDecl: + // Check for top-level function + if d.Recv == nil && d.Name != nil && d.Name.Obj != nil { + topDecls[d.Name.Obj] = d + } + } + } +} + +// collectUnresolved collects unresolved identifiers and declarations. +func collectUnresolved(file *ast.File, topDecls map[*ast.Object]ast.Decl) map[string]map[string]bool { + unresolved := map[string]map[string]bool{} + unresolvedList := []*ast.Ident{} + for _, u := range file.Unresolved { + if _, ok := unresolved[u.Name]; ok { + continue + } + + if isPredeclared(u.Name) { + continue + } + + unresolved[u.Name] = map[string]bool{} + unresolvedList = append(unresolvedList, u) + } + + ast.Inspect(file, func(n ast.Node) bool { + switch e := n.(type) { + case *ast.Ident: + if d := topDecls[e.Obj]; d != nil { + delete(unresolved, e.Name) + } + case *ast.SelectorExpr: + for _, u := range unresolvedList { + if u == e.X { + ident := e.X.(*ast.Ident) + unresolved[ident.Name][e.Sel.Name] = true + break + } + } + } + + return true + }) + + // Delete unresolved identifier without any selector + for u, v := range unresolved { + if len(v) == 0 { // no selector + delete(unresolved, u) + } + } + + return unresolved +} + +// cleanupPreviousImports removes resolved imports from the unresolved list. +func (p *Processor) cleanupPreviousImports(node *ast.File, knownDecls map[*ast.Object]ast.Decl, unresolved map[string]map[string]bool) { + imports := astutil.Imports(p.fset, node) + for _, imps := range imports { + for _, imp := range imps { + pkgpath := imp.Path.Value[1 : len(imp.Path.Value)-1] // unquote the value + + name := filepath.Base(pkgpath) + if pkg := p.resolver.ResolvePath(pkgpath); pkg != nil { + name = pkg.Name() + } + + isNamedImport := imp.Name != nil && imp.Name.Name != "_" + if isNamedImport { + name = imp.Name.Name + } + + if _, ok := unresolved[name]; ok { + delete(unresolved, name) + continue + } + + if isNamedImport { + astutil.DeleteNamedImport(p.fset, node, name, pkgpath) + } else { + astutil.DeleteImport(p.fset, node, pkgpath) + } + } + } + + // Mark knownDecls as resolved + for obj := range knownDecls { + delete(unresolved, obj.Name) + } +} + +// resolve tries to resolve unresolved package using `Resolver` +func (p *Processor) resolve( + node *ast.File, + unresolved map[string]map[string]bool, +) { + for decl, sels := range unresolved { + for _, pkg := range p.resolver.ResolveName(decl) { + if !hasDeclExposed(p, sels, pkg) { + continue + } + + astutil.AddImport(p.fset, node, pkg.Path()) + delete(unresolved, decl) + break + } + } +} + +// hasDeclExposed checks if declarations are exposed in the specified path. +func hasDeclExposed(p *Processor, decls map[string]bool, pkg Package) bool { + exposed := p.processPackageFiles(pkg.Path(), pkg) + if exposed.error != nil { + return false + } + + for obj := range exposed.decls { + if !ast.IsExported(obj.Name) { + continue + } + + if decls[obj.Name] { + return true + } + } + + return false +} diff --git a/gnovm/pkg/gnofmt/resolver.go b/gnovm/pkg/gnofmt/resolver.go new file mode 100644 index 00000000000..7441f488b5f --- /dev/null +++ b/gnovm/pkg/gnofmt/resolver.go @@ -0,0 +1,110 @@ +package gnofmt + +import ( + "fmt" + "go/token" + "io/fs" + "path/filepath" + "strings" +) + +type Resolver interface { + // ResolveName should resolve the given package name by returning a list + // of packages matching the given name + ResolveName(pkgname string) []Package + // ResolvePath should resolve the given package path by returning a + // single package + ResolvePath(pkgpath string) Package +} + +type FSResolver struct { + fset *token.FileSet + visited map[string]bool + pkgpath map[string]Package // pkg path -> pkg + pkgs map[string][]Package // pkg name -> []pkg +} + +func NewFSResolver() *FSResolver { + return &FSResolver{ + fset: token.NewFileSet(), + visited: map[string]bool{}, + pkgpath: map[string]Package{}, + pkgs: map[string][]Package{}, + } +} + +func (r *FSResolver) ResolveName(pkgname string) []Package { + // First stdlibs, then external packages + return r.pkgs[pkgname] +} + +func (r *FSResolver) ResolvePath(pkgpath string) Package { + return r.pkgpath[pkgpath] +} + +// PackageHandler is a callback passed to the resolver during package loading. +// PackageHandler will be called on each package. If no error is passed, that +// means that the package has been fully loaded. +// If any handled error is returned from the handler, the package process will +// immediately stop. +type PackageHandler func(path string, err error) error + +func basicPkgHandler(path string, err error) error { + return err +} + +// LoadPackages lists all packages in the directory (excluding those which can't be processed). +func (r *FSResolver) LoadPackages(root string, pkgHandler PackageHandler) error { + if pkgHandler == nil { + pkgHandler = basicPkgHandler + } + + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err // skip error + } + + if !d.IsDir() { + return nil + } + + if strings.HasPrefix(d.Name(), ".") { + return filepath.SkipDir + } + + // Skip already visited dir + if r.visited[path] { + return filepath.SkipDir + } + r.visited[path] = true + + pkg, err := ParsePackage(r.fset, root, path) + if err != nil { + return pkgHandler( + path, + fmt.Errorf("unable to inspect package %q: %w", path, err), + ) + } + + if pkg == nil || pkg.Path() == "" { + // not a package + return nil + } + + // Check for conflict with previous import path + if _, ok := r.pkgpath[pkg.Path()]; ok { + // Stop on path conflict, has a package path should be uniq + return pkgHandler( + path, + fmt.Errorf("%q has been declared twice", pkg.Path()), + ) + } + + r.pkgpath[pkg.Path()] = pkg + r.pkgs[pkg.Name()] = append(r.pkgs[pkg.Name()], pkg) + + return pkgHandler(path, nil) + }) + + return err +} diff --git a/gnovm/pkg/gnofmt/resolver_mock.go b/gnovm/pkg/gnofmt/resolver_mock.go new file mode 100644 index 00000000000..8ee35f3198f --- /dev/null +++ b/gnovm/pkg/gnofmt/resolver_mock.go @@ -0,0 +1,86 @@ +package gnofmt + +import ( + "bytes" + "fmt" + "io" +) + +type mockResolver struct { + pkgspath map[string]Package // pkg path -> pkg + pkgs map[string][]Package // pkg name -> []pkg +} + +func newMockResolver() *mockResolver { + return &mockResolver{ + pkgspath: make(map[string]Package), + pkgs: make(map[string][]Package), + } +} + +type mockPackage struct { + PkgPath string + PkgName string + filesname []string + files [][]byte +} + +func newMockedPackage(path, name string) *mockPackage { + return &mockPackage{PkgPath: path, PkgName: name} +} + +func (m *mockPackage) AddFile(filename string, body []byte) { + m.filesname = append(m.filesname, filename) + m.files = append(m.files, body) +} + +// Should return the package path +func (m *mockPackage) Path() string { + return m.PkgPath +} + +// Should return the name of the as definied at the top level of each +// files +func (m *mockPackage) Name() string { + return m.PkgName +} + +// Should return all gno filename inside the package +func (m *mockPackage) Files() []string { + return m.filesname +} + +// ReaderCloser wraps an io.Reader and provides a no-op Close method. +type readerCloser struct { + io.Reader +} + +func (readerCloser) Close() error { return nil } + +// Should return a content reader for the the given filename within the package +func (m *mockPackage) Read(filename string) (io.ReadCloser, error) { + for i, file := range m.filesname { + if file != filename { + continue + } + + r := bytes.NewReader(m.files[i]) + return &readerCloser{r}, nil + } + + return nil, fmt.Errorf("file not found %q", filename) +} + +func (m *mockResolver) AddPackage(pkg Package) []Package { + m.pkgs[pkg.Name()] = append(m.pkgs[pkg.Name()], pkg) + m.pkgspath[pkg.Path()] = pkg + return nil +} + +func (m *mockResolver) ResolveName(pkgname string) []Package { + return m.pkgs[pkgname] +} + +func (m *mockResolver) ResolvePath(pkgpath string) Package { + return m.pkgspath[pkgpath] +} diff --git a/gnovm/pkg/gnofmt/utils.go b/gnovm/pkg/gnofmt/utils.go new file mode 100644 index 00000000000..95e0fb53c3b --- /dev/null +++ b/gnovm/pkg/gnofmt/utils.go @@ -0,0 +1,69 @@ +package gnofmt + +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") +} + +// isPredeclared reports whether an identifier is predeclared. +func isPredeclared(s string) bool { + return predeclaredTypes[s] || predeclaredFuncs[s] || predeclaredConstants[s] +} + +var ( + predeclaredTypes = map[string]bool{ + "any": true, + "bool": true, + "byte": true, + "comparable": true, + "complex64": true, + "complex128": true, + "error": true, + "float32": true, + "float64": true, + "int": true, + "int8": true, + "int16": true, + "int32": true, + "int64": true, + "rune": true, + "string": true, + "uint": true, + "uint8": true, + "uint16": true, + "uint32": true, + "uint64": true, + "uintptr": true, + } + predeclaredFuncs = map[string]bool{ + "append": true, + "cap": true, + "close": true, + "complex": true, + "copy": true, + "delete": true, + "imag": true, + "len": true, + "make": true, + "new": true, + "panic": true, + "print": true, + "println": true, + "real": true, + "recover": true, + } + predeclaredConstants = map[string]bool{ + "false": true, + "iota": true, + "nil": true, + "true": true, + } +) diff --git a/gnovm/pkg/gnolang/alloc.go b/gnovm/pkg/gnolang/alloc.go index 495be0d2dc2..6fef5eda834 100644 --- a/gnovm/pkg/gnolang/alloc.go +++ b/gnovm/pkg/gnolang/alloc.go @@ -62,6 +62,7 @@ const ( // allocPackge = 1 allocAmino = _allocBase + _allocPointer + _allocAny allocAminoByte = 10 // XXX + allocHeapItem = _allocBase + _allocPointer + _allocTypedValue ) func NewAllocator(maxBytes int64) *Allocator { @@ -180,6 +181,10 @@ func (alloc *Allocator) AllocateAmino(l int64) { alloc.Allocate(allocAmino + allocAminoByte*l) } +func (alloc *Allocator) AllocateHeapItem() { + alloc.Allocate(allocHeapItem) +} + //---------------------------------------- // constructor utilities. @@ -291,3 +296,8 @@ func (alloc *Allocator) NewType(t Type) Type { alloc.AllocateType() return t } + +func (alloc *Allocator) NewHeapItem(tv TypedValue) *HeapItemValue { + alloc.AllocateHeapItem() + return &HeapItemValue{Value: tv} +} diff --git a/gnovm/pkg/gnolang/debug.go b/gnovm/pkg/gnolang/debug.go index c6e39dfaa5a..c7f9311ffe4 100644 --- a/gnovm/pkg/gnolang/debug.go +++ b/gnovm/pkg/gnolang/debug.go @@ -3,7 +3,6 @@ package gnolang import ( "fmt" "net/http" - "os" "strings" "time" @@ -23,10 +22,8 @@ type debugging bool // using a const is probably faster. // const debug debugging = true // or flip -var debug debugging = false func init() { - debug = os.Getenv("DEBUG") == "1" if debug { go func() { // e.g. @@ -48,16 +45,16 @@ func init() { var enabled bool = true -func (d debugging) Println(args ...interface{}) { - if d { +func (debugging) Println(args ...interface{}) { + if debug { if enabled { fmt.Println(append([]interface{}{"DEBUG:"}, args...)...) } } } -func (d debugging) Printf(format string, args ...interface{}) { - if d { +func (debugging) Printf(format string, args ...interface{}) { + if debug { if enabled { fmt.Printf("DEBUG: "+format, args...) } @@ -69,8 +66,8 @@ var derrors []string = nil // Instead of actually panic'ing, which messes with tests, errors are sometimes // collected onto `var derrors`. tests/file_test.go checks derrors after each // test, and the file test fails if any unexpected debug errors were found. -func (d debugging) Errorf(format string, args ...interface{}) { - if d { +func (debugging) Errorf(format string, args ...interface{}) { + if debug { if enabled { derrors = append(derrors, fmt.Sprintf(format, args...)) } diff --git a/gnovm/pkg/gnolang/debug_false.go b/gnovm/pkg/gnolang/debug_false.go new file mode 100644 index 00000000000..ce714452be7 --- /dev/null +++ b/gnovm/pkg/gnolang/debug_false.go @@ -0,0 +1,5 @@ +//go:build !debug + +package gnolang + +const debug debugging = false diff --git a/gnovm/pkg/gnolang/debug_true.go b/gnovm/pkg/gnolang/debug_true.go new file mode 100644 index 00000000000..e29b4d4a67c --- /dev/null +++ b/gnovm/pkg/gnolang/debug_true.go @@ -0,0 +1,5 @@ +//go:build debug + +package gnolang + +const debug debugging = true diff --git a/gnovm/pkg/gnolang/debugger.go b/gnovm/pkg/gnolang/debugger.go index 2a080da47a3..839b6a691de 100644 --- a/gnovm/pkg/gnolang/debugger.go +++ b/gnovm/pkg/gnolang/debugger.go @@ -43,14 +43,29 @@ 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 + 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 +} + +// 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) { + d.in = in + d.out = out + d.enabled = true + d.state = DebugAtInit + d.getSrc = f +} + +// Disable makes the debugger d inactive. +func (d *Debugger) Disable() { + d.enabled = false } type debugCommand struct { @@ -132,13 +147,11 @@ loop: continue loop } default: - for _, b := range m.Debugger.breakpoints { - if b == m.Debugger.loc && m.Debugger.loc != m.Debugger.prevLoc { - m.Debugger.state = DebugAtCmd - m.Debugger.prevLoc = m.Debugger.loc - debugList(m, "") - continue loop - } + if atBreak(m) { + m.Debugger.state = DebugAtCmd + m.Debugger.prevLoc = m.Debugger.loc + debugList(m, "") + continue loop } } break loop @@ -146,6 +159,7 @@ loop: os.Exit(0) } } + m.Debugger.prevLoc = m.Debugger.loc debugUpdateLocation(m) // Keep track of exact locations when performing calls. @@ -158,6 +172,20 @@ loop: } } +// atBreak returns true if current machine location matches a breakpoint, false otherwise. +func atBreak(m *Machine) bool { + loc := m.Debugger.loc + if loc == m.Debugger.prevLoc { + return false + } + for _, b := range m.Debugger.breakpoints { + if loc.File == b.File && loc.Line == b.Line { + return true + } + } + return false +} + // debugCmd processes a debugger REPL command. It displays a prompt, then // reads and parses a command from the debugger input stream, then executes // the corresponding function or returns an error. @@ -230,6 +258,7 @@ func debugUpdateLocation(m *Machine) { expr := m.Exprs[i] if l := expr.GetLine(); l > 0 { m.Debugger.loc.Line = l + m.Debugger.loc.Column = expr.GetColumn() return } } @@ -238,6 +267,7 @@ func debugUpdateLocation(m *Machine) { if stmt := m.PeekStmt1(); stmt != nil { if l := stmt.GetLine(); l > 0 { m.Debugger.loc.Line = l + m.Debugger.loc.Column = stmt.GetColumn() return } } @@ -469,7 +499,13 @@ func debugList(m *Machine, arg string) (err error) { } src, err := fileContent(m.Store, loc.PkgPath, loc.File) if err != nil { - return err + // Use optional getSrc helper as fallback to get source. + if m.Debugger.getSrc != nil { + src = m.Debugger.getSrc(loc.File) + } + if src == "" { + return err + } } lines, offset := linesAround(src, loc.Line, 10) for i, l := range lines { @@ -698,7 +734,7 @@ func debugLookup(m *Machine, name string) (tv TypedValue, ok bool) { } } // Fallback: search a global value. - if v := sblocks[0].Source.GetValueRef(m.Store, Name(name)); v != nil { + if v := sblocks[0].Source.GetValueRef(m.Store, Name(name), true); v != nil { return *v, true } return tv, false diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index ca50e243a5c..10d7c5ce250 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -23,38 +23,54 @@ type writeNopCloser struct{ io.Writer } func (writeNopCloser) Close() error { return nil } -func eval(debugAddr, in, file string) (string, string, error) { - out := bytes.NewBufferString("") - err := bytes.NewBufferString("") +// TODO (Marc): move evalTest to gnovm/tests package and remove code duplicates +func evalTest(debugAddr, in, file string) (out, err string) { + bout := bytes.NewBufferString("") + berr := bytes.NewBufferString("") stdin := bytes.NewBufferString(in) - stdout := writeNopCloser{out} - stderr := writeNopCloser{err} + stdout := writeNopCloser{bout} + stderr := writeNopCloser{berr} + debug := in != "" || debugAddr != "" + mode := tests.ImportModeStdlibsPreferred + if strings.HasSuffix(file, "_native.gno") { + mode = tests.ImportModeNativePreferred + } + + defer func() { + if r := recover(); r != nil { + err = fmt.Sprintf("%v", r) + } + out = strings.TrimSpace(out) + err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) + }() - testStore := tests.TestStore(gnoenv.RootDir(), "", stdin, stdout, stderr, tests.ImportModeStdlibsPreferred) + testStore := tests.TestStore(gnoenv.RootDir(), "../../tests/files", stdin, stdout, stderr, mode) f := gnolang.MustReadFile(file) m := gnolang.NewMachineWithOptions(gnolang.MachineOptions{ - PkgPath: string(f.PkgName), - Input: stdin, - Output: stdout, - Store: testStore, - Debug: true, - DebugAddr: debugAddr, + PkgPath: string(f.PkgName), + Input: stdin, + Output: stdout, + Store: testStore, + Context: tests.TestContext(string(f.PkgName), nil), + Debug: debug, }) defer m.Release() if debugAddr != "" { - if err := m.Debugger.Serve(debugAddr); err != nil { - return "", "", err + if e := m.Debugger.Serve(debugAddr); e != nil { + err = e.Error() + return } } m.RunFiles(f) ex, _ := gnolang.ParseExpr("main()") m.Eval(ex) - return out.String(), err.String(), nil + out, err = bout.String(), berr.String() + return } func runDebugTest(t *testing.T, targetPath string, tests []dtest) { @@ -62,10 +78,10 @@ func runDebugTest(t *testing.T, targetPath string, tests []dtest) { for _, test := range tests { t.Run("", func(t *testing.T) { - out, err, _ := eval("", test.in, targetPath) + out, err := evalTest("", test.in, targetPath) t.Log("in:", test.in, "out:", out, "err:", err) if !strings.Contains(out, test.out) { - t.Errorf("result does not contain \"%s\", got \"%s\"", test.out, out) + t.Errorf("unexpected output\nwant\"%s\"\n got \"%s\"", test.out, out) } }) } @@ -77,13 +93,13 @@ func TestDebug(t *testing.T) { cont2 := "break 21\ncontinue\n" runDebugTest(t, debugTarget, []dtest{ - {in: "", out: "Welcome to the Gnovm debugger. Type 'help' for list of commands."}, + {in: "\n", out: "Welcome to the Gnovm debugger. Type 'help' for list of commands."}, {in: "help\n", out: "The following commands are available"}, {in: "h\n", out: "The following commands are available"}, {in: "help b\n", out: "Set a breakpoint."}, {in: "help zzz\n", out: "command not available"}, {in: "list " + debugTarget + ":1\n", out: "1: // This is a sample target"}, - {in: "l 55\n", out: "42: }"}, + {in: "l 55\n", out: "46: }"}, {in: "l xxx:0\n", out: "xxx: no such file or directory"}, {in: "l :xxx\n", out: `"xxx": invalid syntax`}, {in: brk, out: "Breakpoint 0 at main "}, @@ -101,14 +117,14 @@ func TestDebug(t *testing.T) { {in: "p 'a'\n", out: "(97 int32)"}, {in: "p '界'\n", out: "(30028 int32)"}, {in: "p \"xxxx\"\n", out: `("xxxx" string)`}, - {in: "si\n", out: "sample.gno:4"}, - {in: "s\ns\n", out: "=> 33: num := 5"}, + {in: "si\n", out: "sample.gno:14"}, + {in: "s\ns\n", out: `=> 14: var global = "test"`}, {in: "s\n\n", out: "=> 33: num := 5"}, {in: "foo", out: "command not available: foo"}, {in: "\n\n", out: "dbg> "}, {in: "#\n", out: "dbg> "}, {in: "p foo", out: "Command failed: could not find symbol value for foo"}, - {in: "b +7\nc\n", out: "=> 11:"}, + {in: "b +7\nc\n", out: "=> 21: r := t.A[i]"}, {in: brk + "clear 0\n", out: "dbg> "}, {in: brk + "clear -1\n", out: "Command failed: invalid breakpoint id: -1"}, {in: brk + "clear\n", out: "dbg> "}, @@ -131,6 +147,7 @@ func TestDebug(t *testing.T) { {in: "b 37\nc\np b\n", out: "(3 int)"}, {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)"}, + {in: "b 43\nc\nc\nc\np i\ndetach\n", out: "(1 int)"}, }) runDebugTest(t, "../../tests/files/a1.gno", []dtest{ @@ -154,7 +171,7 @@ func TestRemoteDebug(t *testing.T) { retry int ) - go eval(debugAddress, "", debugTarget) + go evalTest(debugAddress, "", debugTarget) for retry = 100; retry > 0; retry-- { conn, err = net.Dial("tcp", debugAddress) @@ -177,8 +194,10 @@ func TestRemoteDebug(t *testing.T) { } func TestRemoteError(t *testing.T) { - _, _, err := eval(":xxx", "", debugTarget) - if !strings.Contains(err.Error(), "tcp/xxx: unknown port") { + _, err := evalTest(":xxx", "", debugTarget) + t.Log("err:", err) + if !strings.Contains(err, "tcp/xxx: unknown port") && + !strings.Contains(err, "tcp/xxx: nodename nor servname provided, or not known") { t.Error(err) } } diff --git a/gnovm/pkg/gnolang/eval_test.go b/gnovm/pkg/gnolang/eval_test.go new file mode 100644 index 00000000000..fdd8e0204d1 --- /dev/null +++ b/gnovm/pkg/gnolang/eval_test.go @@ -0,0 +1,62 @@ +package gnolang_test + +import ( + "os" + "path" + "strings" + "testing" +) + +func TestEvalFiles(t *testing.T) { + dir := "../../tests/files" + files, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + for _, f := range files { + wantOut, wantErr, ok := testData(dir, f) + if !ok { + continue + } + t.Run(f.Name(), func(t *testing.T) { + out, err := evalTest("", "", path.Join(dir, f.Name())) + + if wantErr != "" && !strings.Contains(err, wantErr) || + wantErr == "" && err != "" { + t.Fatalf("unexpected error\nWant: %s\n Got: %s", wantErr, err) + } + if wantOut != "" && out != wantOut { + t.Fatalf("unexpected output\nWant: %s\n Got: %s", wantOut, out) + } + }) + } +} + +// testData returns the expected output and error string, and true if entry is valid. +func testData(dir string, f os.DirEntry) (testOut, testErr string, ok bool) { + if f.IsDir() { + return "", "", false + } + name := path.Join(dir, f.Name()) + if !strings.HasSuffix(name, ".gno") || strings.HasSuffix(name, "_long.gno") { + return "", "", false + } + buf, err := os.ReadFile(name) + if err != nil { + return "", "", false + } + str := string(buf) + if strings.Contains(str, "// PKGPATH:") { + return "", "", false + } + return commentFrom(str, "\n// Output:"), commentFrom(str, "\n// Error:"), true +} + +// commentFrom returns the content from a trailing comment block in s starting with delim. +func commentFrom(s, delim string) string { + index := strings.Index(s, delim) + if index < 0 { + return "" + } + return strings.TrimSpace(strings.ReplaceAll(s[index+len(delim):], "\n// ", "\n")) +} diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 741b2158a08..54d808faefc 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -42,92 +42,6 @@ func BenchmarkStringLargeData(b *testing.B) { } } -func TestRunInvalidLabels(t *testing.T) { - tests := []struct { - code string - output string - }{ - { - code: ` - package test - func main(){} - func invalidLabel() { - FirstLoop: - for i := 0; i < 10; i++ { - } - for i := 0; i < 10; i++ { - break FirstLoop - } - } -`, - output: `cannot find branch label "FirstLoop"`, - }, - { - code: ` - package test - func main(){} - - func undefinedLabel() { - for i := 0; i < 10; i++ { - break UndefinedLabel - } - } -`, - output: `label UndefinedLabel undefined`, - }, - { - code: ` - package test - func main(){} - - func labelOutsideScope() { - for i := 0; i < 10; i++ { - continue FirstLoop - } - FirstLoop: - for i := 0; i < 10; i++ { - } - } -`, - output: `cannot find branch label "FirstLoop"`, - }, - { - code: ` - package test - func main(){} - - func invalidLabelStatement() { - if true { - break InvalidLabel - } - } -`, - output: `label InvalidLabel undefined`, - }, - } - - for n, s := range tests { - n := n - t.Run(fmt.Sprintf("%v\n", n), func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - es := fmt.Sprintf("%v\n", r) - if !strings.Contains(es, s.output) { - t.Fatalf("invalid label test: `%v` missing expected error: %+v got: %v\n", n, s.output, es) - } - } else { - t.Fatalf("invalid label test: `%v` should have failed but didn't\n", n) - } - }() - - m := NewMachine("test", nil) - nn := MustParseFile("main.go", s.code) - m.RunFiles(nn) - m.RunMain() - }) - } -} - func TestBuiltinIdentifiersShadowing(t *testing.T) { t.Parallel() tests := map[string]string{} @@ -245,6 +159,38 @@ func main() { m.RunMain() } +func BenchmarkPreprocessForLoop(b *testing.B) { + m := NewMachine("test", nil) + c := `package test +func main() { + for i:=0; i<10000; i++ {} +}` + n := MustParseFile("main.go", c) + m.RunFiles(n) + + for i := 0; i < b.N; i++ { + m.RunMain() + } +} + +func BenchmarkIfStatement(b *testing.B) { + m := NewMachine("test", nil) + c := `package test +func main() { + for i:=0; i<10000; i++ { + if i > 10 { + + } + } +}` + n := MustParseFile("main.go", c) + m.RunFiles(n) + + for i := 0; i < b.N; i++ { + m.RunMain() + } +} + func TestDoOpEvalBaseConversion(t *testing.T) { m := NewMachine("test", nil) diff --git a/gnovm/pkg/gnolang/gnolang.proto b/gnovm/pkg/gnolang/gnolang.proto index f7eaa907ec5..eee9a0375e6 100644 --- a/gnovm/pkg/gnolang/gnolang.proto +++ b/gnovm/pkg/gnolang/gnolang.proto @@ -58,6 +58,8 @@ message FuncValue { google.protobuf.Any closure = 5 [json_name = "Closure"]; string file_name = 6 [json_name = "FileName"]; string pkg_path = 7 [json_name = "PkgPath"]; + string native_pkg = 8 [json_name = "NativePkg"]; + string native_name = 9 [json_name = "NativeName"]; } message MapValue { @@ -140,7 +142,7 @@ message Location { string pkg_path = 1 [json_name = "PkgPath"]; string file = 2 [json_name = "File"]; sint64 line = 3 [json_name = "Line"]; - sint64 nonce = 4 [json_name = "Nonce"]; + sint64 column = 4 [json_name = "Column"]; } message Attributes { @@ -600,4 +602,4 @@ message tupleType { message RefType { string id = 1 [json_name = "ID"]; -} \ No newline at end of file +} diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 5c74621c973..efdfecf0289 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -31,8 +31,10 @@ package gnolang */ import ( + "bytes" "fmt" "go/ast" + "go/format" "go/parser" "go/token" "go/types" @@ -137,6 +139,7 @@ func ParseFile(filename string, body string) (fn *FileNode, err error) { func setLoc(fs *token.FileSet, pos token.Pos, n Node) Node { posn := fs.Position(pos) n.SetLine(posn.Line) + n.SetColumn(posn.Column) return n } @@ -478,6 +481,7 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { //---------------------------------------- // type checking (using go/types) +// XXX move to gotypecheck.go. // MemPackageGetter implements the GetMemPackage() method. It is a subset of // [Store], separated for ease of testing. @@ -489,7 +493,10 @@ type MemPackageGetter interface { // mempkg. To retrieve dependencies, it uses getter. // // The syntax checking is performed entirely using Go's go/types package. -func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { +// +// If format is true, the code will be automatically updated with the +// formatted source code. +func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter, format bool) error { var errs error imp := &gnoImporter{ getter: getter, @@ -502,7 +509,7 @@ func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error } imp.cfg.Importer = imp - _, err := imp.parseCheckMemPackage(mempkg) + _, err := imp.parseCheckMemPackage(mempkg, format) // prefer to return errs instead of err: // err will generally contain only the first error encountered. if errs != nil { @@ -543,12 +550,13 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac g.cache[path] = gnoImporterResult{err: err} return nil, err } - result, err := g.parseCheckMemPackage(mpkg) + fmt := false + result, err := g.parseCheckMemPackage(mpkg, fmt) g.cache[path] = gnoImporterResult{pkg: result, err: err} return result, err } -func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package, error) { +func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage, fmt bool) (*types.Package, error) { fset := token.NewFileSet() files := make([]*ast.File, 0, len(mpkg.Files)) var errs error @@ -565,6 +573,17 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package continue } + // enforce formatting + if fmt { + var buf bytes.Buffer + err = format.Node(&buf, fset, f) + if err != nil { + errs = multierr.Append(errs, err) + continue + } + file.Body = buf.String() + } + files = append(files, f) } if errs != nil { @@ -767,6 +786,7 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { Const: true, } cd.SetAttribute(ATTR_IOTA, si) + setLoc(fs, s.Pos(), cd) ds = append(ds, cd) } else { var names []NameExpr @@ -785,6 +805,7 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { Values: values, Const: false, } + setLoc(fs, s.Pos(), vd) ds = append(ds, vd) } case *ast.ImportSpec: @@ -792,16 +813,19 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { if err != nil { panic("unexpected import spec path type") } - ds = append(ds, &ImportDecl{ + im := &ImportDecl{ NameExpr: *Nx(toName(s.Name)), PkgPath: path, - }) + } + setLoc(fs, s.Pos(), im) + ds = append(ds, im) default: panic(fmt.Sprintf( "unexpected decl spec %v", reflect.TypeOf(s))) } } + return ds } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 995ca3e8c0e..d85c142ca52 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -324,7 +324,8 @@ func TestTypeCheckMemPackage(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - err := TypeCheckMemPackage(tc.pkg, tc.getter) + format := false + err := TypeCheckMemPackage(tc.pkg, tc.getter, format) if tc.check == nil { assert.NoError(t, err) } else { @@ -333,3 +334,46 @@ func TestTypeCheckMemPackage(t *testing.T) { }) } } + +func TestTypeCheckMemPackage_format(t *testing.T) { + t.Parallel() + + input := ` + package hello + func Hello(name string) string {return "hello" + name +} + + + +` + + pkg := &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: input, + }, + }, + } + + mpkgGetter := mockPackageGetter{} + format := false + err := TypeCheckMemPackage(pkg, mpkgGetter, format) + assert.NoError(t, err) + assert.Equal(t, input, pkg.Files[0].Body) // unchanged + + expected := `package hello + +func Hello(name string) string { + return "hello" + name +} +` + + format = true + err = TypeCheckMemPackage(pkg, mpkgGetter, format) + assert.NoError(t, err) + assert.NotEqual(t, input, pkg.Files[0].Body) + assert.Equal(t, expected, pkg.Files[0].Body) +} diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index f73a5c58962..6127fa42b07 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -890,7 +890,7 @@ func gno2GoType(t Type) reflect.Type { // If gno2GoTypeMatches(t, rt) is true, a t value can // be converted to an rt native value using gno2GoValue(v, rv). -// This is called when autoNative is true in checkType(). +// This is called when autoNative is true in assertAssignableTo(). // This is used for all native function calls, and also // for testing whether a native value implements a gno interface. func gno2GoTypeMatches(t Type, rt reflect.Type) (result bool) { diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index 42729b43699..fa5415a8068 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -41,7 +41,7 @@ func TestGoNativeDefine(t *testing.T) { pkg := NewPackageNode("foo", "test.foo", nil) rt := reflect.TypeOf(Foo{}) pkg.DefineGoNativeType(rt) - nt := pkg.GetValueRef(nil, Name("Foo")).GetType().(*NativeType) + nt := pkg.GetValueRef(nil, Name("Foo"), true).GetType().(*NativeType) assert.Equal(t, rt, nt.Type) path := pkg.GetPathForName(nil, Name("Foo")) assert.Equal(t, uint8(1), path.Depth) diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index b163b6a52a7..c6f7e696ea4 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -7,6 +7,34 @@ import ( "strings" ) +// ---------------------------------------- +// Functions centralizing definitions + +// RealmPathPrefix is the prefix used to identify pkgpaths which are meant to +// be realms and as such to have their state persisted. This is used by [IsRealmPath]. +const RealmPathPrefix = "gno.land/r/" + +// ReGnoRunPath is the path used for realms executed in maketx run. +// These are not considered realms, as an exception to the RealmPathPrefix rule. +var ReGnoRunPath = regexp.MustCompile(`^gno\.land/r/g[a-z0-9]+/run$`) + +// IsRealmPath determines whether the given pkgpath is for a realm, and as such +// should persist the global state. +func IsRealmPath(pkgPath string) bool { + return strings.HasPrefix(pkgPath, RealmPathPrefix) && + // MsgRun pkgPath aren't realms + !ReGnoRunPath.MatchString(pkgPath) +} + +// IsStdlib determines whether s is a pkgpath for a standard library. +func IsStdlib(s string) bool { + // NOTE(morgan): this is likely to change in the future as we add support for + // IBC/ICS and we allow import paths to other chains. It might be good to + // (eventually) follow the same rule as Go, which is: does the first + // element of the import path contain a dot? + return !strings.HasPrefix(s, "gno.land/") +} + // ---------------------------------------- // AST Construction (Expr) // These are copied over from go-amino-x, but produces Gno ASTs. @@ -91,7 +119,7 @@ func Flds(args ...interface{}) FieldTypeExprs { func Recv(n, t interface{}) FieldTypeExpr { if n == "" { - n = "_" + n = blankIdentifier } return FieldTypeExpr{ Name: N(n), diff --git a/gnovm/pkg/gnolang/helpers_test.go b/gnovm/pkg/gnolang/helpers_test.go new file mode 100644 index 00000000000..af8fa64ac79 --- /dev/null +++ b/gnovm/pkg/gnolang/helpers_test.go @@ -0,0 +1,55 @@ +package gnolang + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsRealmPath(t *testing.T) { + t.Parallel() + tt := []struct { + input string + result bool + }{ + {"gno.land/r/demo/users", true}, + {"gno.land/r/hello", true}, + {"gno.land/p/demo/users", false}, + {"gno.land/p/hello", false}, + {"gno.land/x", false}, + {"std", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsRealmPath(tc.input), + "unexpected IsRealmPath(%q) result", tc.input, + ) + } +} + +func TestIsStdlib(t *testing.T) { + t.Parallel() + + tt := []struct { + s string + result bool + }{ + {"std", true}, + {"math", true}, + {"very/long/path/with_underscores", true}, + {"gno.land/r/demo/users", false}, + {"gno.land/hello", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsStdlib(tc.s), + "IsStdlib(%q)", tc.s, + ) + } +} diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 68eb44290e2..850da3d3c0f 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -94,7 +94,6 @@ type MachineOptions struct { CheckTypes bool // not yet used ReadOnly bool Debug bool - DebugAddr string // debugger io stream address (stdin/stdout if empty) Input io.Reader // used for default debugger input only Output io.Writer // default os.Stdout Store Store // default NewStore(Alloc, nil, nil) @@ -285,14 +284,29 @@ func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (* } m.SetActivePackage(pv) // run files. - m.RunFiles(files.Files...) - // maybe save package value and mempackage. + updates := m.RunFileDecls(files.Files...) + // save package value and mempackage. + // XXX save condition will be removed once gonative is removed. + var throwaway *Realm if save { - // store package values and types - m.savePackageValuesAndTypes() + // store new package values and types + throwaway = m.saveNewPackageValuesAndTypes() + if throwaway != nil { + m.Realm = throwaway + } + } + // run init functions + m.runInitFromUpdates(pv, updates) + // save again after init. + if save { + m.resavePackageValues(throwaway) // store mempackage m.Store.AddMemPackage(memPkg) + if throwaway != nil { + m.Realm = nil + } } + return pn, pv } @@ -315,7 +329,7 @@ func checkDuplicates(fset *FileSet) bool { name = d.Name case *ValueDecl: for _, nx := range d.NameExprs { - if nx.Name == "_" { + if nx.Name == blankIdentifier { continue } if _, ok := defined[nx.Name]; ok { @@ -327,7 +341,7 @@ func checkDuplicates(fset *FileSet) bool { default: continue } - if name == "_" { + if name == blankIdentifier { continue } if _, ok := defined[name]; ok { @@ -449,11 +463,13 @@ func (m *Machine) injectLocOnPanic() { // Show last location information. // First, determine the line number of expression or statement if any. lastLine := 0 + lastColumn := 0 if len(m.Exprs) > 0 { for i := len(m.Exprs) - 1; i >= 0; i-- { expr := m.Exprs[i] if expr.GetLine() > 0 { lastLine = expr.GetLine() + lastColumn = expr.GetColumn() break } } @@ -463,6 +479,7 @@ func (m *Machine) injectLocOnPanic() { stmt := m.Stmts[i] if stmt.GetLine() > 0 { lastLine = stmt.GetLine() + lastColumn = stmt.GetColumn() break } } @@ -477,6 +494,7 @@ func (m *Machine) injectLocOnPanic() { lastLoc = loc if lastLine > 0 { lastLoc.Line = lastLine + lastLoc.Column = lastColumn } break } @@ -491,13 +509,27 @@ func (m *Machine) injectLocOnPanic() { } } -// Add files to the package's *FileSet and run them. -// This will also run each init function encountered. +// Convenience for tests. +// Production must not use this, because realm package init +// must happen after persistence and realm finalization, +// then changes from init persisted again. func (m *Machine) RunFiles(fns ...*FileNode) { - m.runFiles(fns...) + pv := m.Package + if pv == nil { + panic("RunFiles requires Machine.Package") + } + updates := m.runFileDecls(fns...) + m.runInitFromUpdates(pv, updates) +} + +// 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 { + return m.runFileDecls(fns...) } -func (m *Machine) runFiles(fns ...*FileNode) { +func (m *Machine) runFileDecls(fns ...*FileNode) []TypedValue { // Files' package names must match the machine's active one. // if there is one. for _, fn := range fns { @@ -625,11 +657,15 @@ func (m *Machine) runFiles(fns ...*FileNode) { } } - // Run new init functions. - // Go spec: "To ensure reproducible initialization - // behavior, build systems are encouraged to present - // multiple files belonging to the same package in - // lexical file name order to a compiler." + return updates +} + +// Run new init functions. +// Go spec: "To ensure reproducible initialization +// behavior, build systems are encouraged to present +// multiple files belonging to the same package in +// lexical file name order to a compiler." +func (m *Machine) runInitFromUpdates(pv *PackageValue, updates []TypedValue) { for _, tv := range updates { if tv.IsDefined() && tv.T.Kind() == FuncKind && tv.V != nil { fv, ok := tv.V.(*FuncValue) @@ -648,7 +684,10 @@ func (m *Machine) runFiles(fns ...*FileNode) { // Save the machine's package using realm finalization deep crawl. // Also saves declared types. -func (m *Machine) savePackageValuesAndTypes() { +// This happens before any init calls. +// Returns a throwaway realm package is not a realm, +// such as stdlibs or /p/ packages. +func (m *Machine) saveNewPackageValuesAndTypes() (throwaway *Realm) { // save package value and dependencies. pv := m.Package if pv.IsRealm() { @@ -661,6 +700,7 @@ func (m *Machine) savePackageValuesAndTypes() { rlm := NewRealm(pv.PkgPath) rlm.MarkNewReal(pv) rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + throwaway = rlm } // save declared types. if bv, ok := pv.Block.(*Block); ok { @@ -672,6 +712,25 @@ func (m *Machine) savePackageValuesAndTypes() { } } } + return +} + +// Resave any changes to realm after init calls. +// Pass in the realm from m.saveNewPackageValuesAndTypes() +// in case a throwaway was created. +func (m *Machine) resavePackageValues(rlm *Realm) { + // save package value and dependencies. + pv := m.Package + if pv.IsRealm() { + rlm = pv.Realm + rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + // re-save package realm info. + m.Store.SetPackageRealm(rlm) + } else { // use the throwaway realm. + rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + } + // types were already saved, and should not change + // even after running the init function. } func (m *Machine) RunFunc(fn Name) { @@ -812,6 +871,13 @@ func (m *Machine) RunStatement(s Stmt) { // NOTE: to support realm persistence of types, must // first require the validation of blocknode locations. func (m *Machine) RunDeclaration(d Decl) { + if fd, ok := d.(*FuncDecl); ok && fd.Name == "init" { + // XXX or, consider running it, but why would this be needed? + // from a repl there is no need for init() functions. + // Also, there are complications with realms, where + // the realm must be persisted before init(), and persisted again. + panic("Machine.RunDeclaration cannot be used for init functions") + } // Preprocess input using package block. There should only // be one block right now, and it's a *PackageNode. pn := m.LastBlock().GetSource(m.Store).(*PackageNode) @@ -1735,8 +1801,23 @@ func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) { if pv == nil { panic(fmt.Sprintf("package value missing in store: %s", fv.PkgPath)) } - m.Package = pv rlm := pv.GetRealm() + if rlm == nil && recv.IsDefined() { + obj := recv.GetFirstObject(m.Store) + if obj == nil { + // could be a nil receiver. + // just ignore. + } else { + recvOID := obj.GetObjectInfo().ID + if !recvOID.IsZero() { + // override the pv and rlm with receiver's. + recvPkgOID := ObjectIDFromPkgID(recvOID.PkgID) + pv = m.Store.GetObject(recvPkgOID).(*PackageValue) + rlm = pv.GetRealm() // done + } + } + } + m.Package = pv if rlm != nil && m.Realm != rlm { m.Realm = rlm // enter new realm } @@ -1957,9 +2038,11 @@ func (m *Machine) PopAsPointer(lx Expr) PointerValue { return ptr case *CompositeLitExpr: // for *RefExpr tv := *m.PopValue() + hv := m.Alloc.NewHeapItem(tv) return PointerValue{ - TV: &tv, // heap alloc - Base: nil, + TV: &hv.Value, + Base: hv, + Index: 0, } default: panic("should not happen") @@ -2058,19 +2141,19 @@ func (m *Machine) String() string { builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Values[i])) } - builder.WriteString(`Exprs: `) + builder.WriteString(" Exprs:\n") for i := len(m.Exprs) - 1; i >= 0; i-- { builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Exprs[i])) } - builder.WriteString(`Stmts: `) + builder.WriteString(" Stmts:\n") for i := len(m.Stmts) - 1; i >= 0; i-- { builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Stmts[i])) } - builder.WriteString(`Blocks: `) + builder.WriteString(" Blocks:\n") for b := m.LastBlock(); b != nil; { gen := builder.Len()/3 + 1 @@ -2107,7 +2190,7 @@ func (m *Machine) String() string { } } - builder.WriteString(`Blocks (other): `) + builder.WriteString(" Blocks (other):\n") for i := len(m.Blocks) - 2; i >= 0; i-- { b := m.Blocks[i] @@ -2119,17 +2202,17 @@ func (m *Machine) String() string { if _, ok := b.Source.(*PackageNode); ok { break // done, skip *PackageNode. } else { - builder.WriteString(fmt.Sprintf(" #%d %s", i, + builder.WriteString(fmt.Sprintf(" #%d %s\n", i, b.StringIndented(" "))) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - builder.WriteString(fmt.Sprintf(" (static) #%d %s", i, + builder.WriteString(fmt.Sprintf(" (static) #%d %s\n", i, sb.StringIndented(" "))) } } } - builder.WriteString(`Frames: `) + builder.WriteString(" Frames:\n") for i := len(m.Frames) - 1; i >= 0; i-- { builder.WriteString(fmt.Sprintf(" #%d %s\n", i, m.Frames[i])) @@ -2139,7 +2222,7 @@ func (m *Machine) String() string { builder.WriteString(fmt.Sprintf(" Realm:\n %s\n", m.Realm.Path)) } - builder.WriteString(`Exceptions: `) + builder.WriteString(" Exceptions:\n") for _, ex := range m.Exceptions { builder.WriteString(fmt.Sprintf(" %s\n", ex.Sprint(m))) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 8f2c5054a8a..c578d2a3ec3 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -118,31 +118,23 @@ type Location struct { PkgPath string File string Line int - Nonce int + Column int } func (loc Location) String() string { - if loc.Nonce == 0 { - return fmt.Sprintf("%s/%s:%d", - loc.PkgPath, - loc.File, - loc.Line, - ) - } else { - return fmt.Sprintf("%s/%s:%d#%d", - loc.PkgPath, - loc.File, - loc.Line, - loc.Nonce, - ) - } + return fmt.Sprintf("%s/%s:%d:%d", + loc.PkgPath, + loc.File, + loc.Line, + loc.Column, + ) } func (loc Location) IsZero() bool { return loc.PkgPath == "" && loc.File == "" && loc.Line == 0 && - loc.Nonce == 0 + loc.Column == 0 } // ---------------------------------------- @@ -153,9 +145,10 @@ func (loc Location) IsZero() bool { // for preprocessing) are stored in .data. type Attributes struct { - Line int - Label Name - data map[interface{}]interface{} // not persisted + Line int + Column int + Label Name + data map[interface{}]interface{} // not persisted } func (attr *Attributes) GetLine() int { @@ -166,6 +159,14 @@ func (attr *Attributes) SetLine(line int) { attr.Line = line } +func (attr *Attributes) GetColumn() int { + return attr.Column +} + +func (attr *Attributes) SetColumn(column int) { + attr.Column = column +} + func (attr *Attributes) GetLabel() Name { return attr.Label } @@ -199,6 +200,8 @@ type Node interface { Copy() Node GetLine() int SetLine(int) + GetColumn() int + SetColumn(int) GetLabel() Name SetLabel(Name) HasAttribute(key interface{}) bool @@ -669,6 +672,10 @@ func (ss Body) GetBody() Body { return ss } +func (ss *Body) SetBody(nb Body) { + *ss = nb +} + func (ss Body) GetLabeledStmt(label Name) (stmt Stmt, idx int) { for idx, stmt = range ss { if label == stmt.GetLabel() { @@ -1014,7 +1021,7 @@ type ValueDecl struct { func (x *ValueDecl) GetDeclNames() []Name { ns := make([]Name, 0, len(x.NameExprs)) for _, nx := range x.NameExprs { - if nx.Name == "_" { + if nx.Name == blankIdentifier { // ignore } else { ns = append(ns, nx.Name) @@ -1031,7 +1038,7 @@ type TypeDecl struct { } func (x *TypeDecl) GetDeclNames() []Name { - if x.NameExpr.Name == "_" { + if x.NameExpr.Name == blankIdentifier { return nil // ignore } else { return []Name{x.NameExpr.Name} @@ -1095,7 +1102,7 @@ func PackageNameFromFileBody(name, body string) Name { // ReadMemPackage initializes a new MemPackage by reading the OS directory // at dir, and saving it with the given pkgPath (import path). // The resulting MemPackage will contain the names and content of all *.gno files, -// and additionally README.md, LICENSE, and gno.mod. +// and additionally README.md, LICENSE. // // ReadMemPackage does not perform validation aside from the package's name; // the files are not parsed but their contents are merely stored inside a MemFile. @@ -1108,18 +1115,27 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { panic(err) } allowedFiles := []string{ // make case insensitive? - "gno.mod", "LICENSE", "README.md", } allowedFileExtensions := []string{ ".gno", } + // exceptions to allowedFileExtensions + var rejectedFileExtensions []string + + if IsStdlib(pkgPath) { + // Allows transpilation to work on stdlibs with native fns. + allowedFileExtensions = append(allowedFileExtensions, ".go") + rejectedFileExtensions = []string{".gen.go"} + } + list := make([]string, 0, len(files)) for _, file := range files { if file.IsDir() || strings.HasPrefix(file.Name(), ".") || - (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) { + (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) || + endsWith(file.Name(), rejectedFileExtensions) { continue } list = append(list, filepath.Join(dir, file.Name())) @@ -1179,7 +1195,7 @@ func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) { } n, err := ParseFile(mfile.Name, mfile.Body) if err != nil { - panic(errors.Wrap(err, "parsing file "+mfile.Name)) + panic(err) } if memPkg.Name != string(n.PkgName) { panic(fmt.Sprintf( @@ -1363,6 +1379,13 @@ func (x *PackageNode) PrepareNewValues(pv *PackageValue) []TypedValue { panic("PackageNode.PrepareNewValues() package mismatch") } } + // The FuncValue Body may have been altered during the preprocessing. + // We need to update body field from the source in the FuncValue accordingly. + for _, tv := range x.Values { + if fv, ok := tv.V.(*FuncValue); ok { + fv.UpdateBodyFromSource() + } + } pvl := len(block.Values) pnl := len(x.Values) // copy new top-level defined values/types. @@ -1410,7 +1433,7 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M panic("should not happen") } } - fv := x.GetValueRef(nil, n).V.(*FuncValue) + fv := x.GetValueRef(nil, n, true).V.(*FuncValue) fv.nativeBody = native } @@ -1424,7 +1447,7 @@ func (x *PackageNode) DefineNativeOverride(n Name, native func(*Machine)) { if native == nil { panic("DefineNative expects a function, but got nil") } - fv := x.GetValueRef(nil, n).V.(*FuncValue) + fv := x.GetValueRef(nil, n, true).V.(*FuncValue) fv.nativeBody = native } @@ -1461,13 +1484,14 @@ type BlockNode interface { GetPathForName(Store, Name) ValuePath GetIsConst(Store, Name) bool GetLocalIndex(Name) (uint16, bool) - GetValueRef(Store, Name) *TypedValue + GetValueRef(Store, Name, bool) *TypedValue GetStaticTypeOf(Store, Name) Type GetStaticTypeOfAt(Store, ValuePath) Type Predefine(bool, Name) Define(Name, TypedValue) Define2(bool, Name, Type, TypedValue) GetBody() Body + SetBody(Body) } // ---------------------------------------- @@ -1588,8 +1612,8 @@ func (sb *StaticBlock) GetParentNode(store Store) BlockNode { // Implements BlockNode. // As a side effect, notes externally defined names. func (sb *StaticBlock) GetPathForName(store Store, n Name) ValuePath { - if n == "_" { - return NewValuePathBlock(0, 0, "_") + if n == blankIdentifier { + return NewValuePathBlock(0, 0, blankIdentifier) } // Check local. gen := 1 @@ -1722,17 +1746,19 @@ func (sb *StaticBlock) GetLocalIndex(n Name) (uint16, bool) { // Implemented BlockNode. // This method is too slow for runtime, but it is used // during preprocessing to compute types. +// If skipPredefined, skips over names that are only predefined. // Returns nil if not defined. -func (sb *StaticBlock) GetValueRef(store Store, n Name) *TypedValue { +func (sb *StaticBlock) GetValueRef(store Store, n Name, skipPredefined bool) *TypedValue { idx, ok := sb.GetLocalIndex(n) bb := &sb.Block bp := sb.GetParentNode(store) for { - if ok { + if ok && (!skipPredefined || sb.Types[idx] != nil) { return bb.GetPointerToInt(store, int(idx)).TV } else if bp != nil { idx, ok = bp.GetLocalIndex(n) - bb = bp.GetStaticBlock().GetBlock() + sb = bp.GetStaticBlock() + bb = sb.GetBlock() bp = bp.GetParentNode(store) } else { return nil @@ -1742,8 +1768,8 @@ func (sb *StaticBlock) GetValueRef(store Store, n Name) *TypedValue { // Implements BlockNode // Statically declares a name definition. -// At runtime, use *Block.GetValueRef() etc which take path -// values, which are pre-computeed in the preprocessor. +// At runtime, use *Block.GetPointerTo() which takes a path +// value, which is pre-computeed in the preprocessor. // Once a typed value is defined, it cannot be changed. // // NOTE: Currently tv.V is only set when the value represents a Type(Value) or @@ -1755,12 +1781,14 @@ func (sb *StaticBlock) Define(n Name, tv TypedValue) { sb.Define2(false, n, tv.T, tv) } +// Set type to nil, only reserving the name. func (sb *StaticBlock) Predefine(isConst bool, n Name) { sb.Define2(isConst, n, nil, anyValue(nil)) } // The declared type st may not be the same as the static tv; // e.g. var x MyInterface = MyStruct{}. +// Setting st and tv to nil/zero reserves (predefines) name for definition later. func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { if debug { debug.Printf( @@ -1780,7 +1808,7 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { if tv.T == nil && tv.V != nil { panic("StaticBlock.Define2() requires .T if .V is set") } - if n == "_" { + if n == blankIdentifier { return // ignore } idx, exists := sb.GetLocalIndex(n) @@ -1857,18 +1885,34 @@ func (x *IfStmt) GetBody() Body { panic("IfStmt has no body (but .Then and .Else do)") } +func (x *IfStmt) SetBody(b Body) { + panic("IfStmt has no body (but .Then and .Else do)") +} + func (x *SwitchStmt) GetBody() Body { panic("SwitchStmt has no body (but its cases do)") } +func (x *SwitchStmt) SetBody(b Body) { + panic("SwitchStmt has no body (but its cases do)") +} + func (x *FileNode) GetBody() Body { panic("FileNode has no body (but it does have .Decls)") } +func (x *FileNode) SetBody(b Body) { + panic("FileNode has no body (but it does have .Decls)") +} + func (x *PackageNode) GetBody() Body { panic("PackageNode has no body") } +func (x *PackageNode) SetBody(b Body) { + panic("PackageNode has no body") +} + // ---------------------------------------- // Value Path diff --git a/gnovm/pkg/gnolang/op_assign.go b/gnovm/pkg/gnolang/op_assign.go index 0cc30861355..eb67ffcc351 100644 --- a/gnovm/pkg/gnolang/op_assign.go +++ b/gnovm/pkg/gnolang/op_assign.go @@ -50,7 +50,7 @@ func (m *Machine) doOpAddAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -73,7 +73,7 @@ func (m *Machine) doOpSubAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -96,7 +96,7 @@ func (m *Machine) doOpMulAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -119,7 +119,7 @@ func (m *Machine) doOpQuoAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -142,7 +142,7 @@ func (m *Machine) doOpRemAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -165,7 +165,7 @@ func (m *Machine) doOpBandAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -188,7 +188,7 @@ func (m *Machine) doOpBandnAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -211,7 +211,7 @@ func (m *Machine) doOpBorAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -234,7 +234,7 @@ func (m *Machine) doOpXorAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 99b56c18a06..a1861ed3aaa 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -45,7 +45,7 @@ func (m *Machine) doOpLor() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set result in lv. @@ -60,7 +60,7 @@ func (m *Machine) doOpLand() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set result in lv. @@ -77,7 +77,7 @@ func (m *Machine) doOpEql() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertEqualityTypes(lv.T, rv.T) + debugAssertEqualityTypes(lv.T, rv.T) } // set result in lv. @@ -94,7 +94,7 @@ func (m *Machine) doOpNeq() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertEqualityTypes(lv.T, rv.T) + debugAssertEqualityTypes(lv.T, rv.T) } // set result in lv. @@ -111,7 +111,7 @@ func (m *Machine) doOpLss() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -128,7 +128,7 @@ func (m *Machine) doOpLeq() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -145,7 +145,7 @@ func (m *Machine) doOpGtr() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -162,7 +162,7 @@ func (m *Machine) doOpGeq() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -179,7 +179,7 @@ func (m *Machine) doOpAdd() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // add rv to lv. @@ -193,7 +193,7 @@ func (m *Machine) doOpSub() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // sub rv from lv. @@ -207,7 +207,7 @@ func (m *Machine) doOpBor() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv | rv @@ -221,7 +221,7 @@ func (m *Machine) doOpXor() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv ^ rv @@ -235,7 +235,7 @@ func (m *Machine) doOpMul() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv * rv @@ -249,7 +249,7 @@ func (m *Machine) doOpQuo() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv / rv @@ -263,7 +263,7 @@ func (m *Machine) doOpRem() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv % rv @@ -309,7 +309,7 @@ func (m *Machine) doOpBand() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv & rv @@ -323,7 +323,7 @@ func (m *Machine) doOpBandn() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv &^ rv diff --git a/gnovm/pkg/gnolang/op_eval.go b/gnovm/pkg/gnolang/op_eval.go index bf4ac88aa25..701615fff13 100644 --- a/gnovm/pkg/gnolang/op_eval.go +++ b/gnovm/pkg/gnolang/op_eval.go @@ -47,7 +47,7 @@ func (m *Machine) doOpEval() { m.PopExpr() switch x.Kind { case INT: - x.Value = strings.ReplaceAll(x.Value, "_", "") + x.Value = strings.ReplaceAll(x.Value, blankIdentifier, "") // temporary optimization bi := big.NewInt(0) // TODO optimize. @@ -84,7 +84,7 @@ func (m *Machine) doOpEval() { V: BigintValue{V: bi}, }) case FLOAT: - x.Value = strings.ReplaceAll(x.Value, "_", "") + x.Value = strings.ReplaceAll(x.Value, blankIdentifier, "") if reFloat.MatchString(x.Value) { value := x.Value diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 12e0f9e26e3..c7e8ffd600c 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -957,7 +957,7 @@ func (m *Machine) doOpSwitchClauseCase() { // eval whether cv == tv. if debug { - assertEqualityTypes(cv.T, tv.T) + debugAssertEqualityTypes(cv.T, tv.T) } match := isEql(m.Store, cv, tv) if match { diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 2efcc41fdce..36130ccbf4d 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -204,23 +204,48 @@ func (m *Machine) doOpRef() { func (m *Machine) doOpTypeAssert1() { m.PopExpr() // pop type - t := m.PopValue().GetType() + t := m.PopValue().GetType() // type being asserted + // peek x for re-use - xv := m.PeekValue(1) - xt := xv.T + xv := m.PeekValue(1) // value result / value to assert + xt := xv.T // underlying value's type + + // xt may be nil, but we need to wait to return because the value of xt that is set + // will depend on whether we are trying to assert to an interface or concrete type. + // xt can be nil in the case where recover can't find a panic to recover from and + // returns a bare TypedValue{}. if t.Kind() == InterfaceKind { // is interface assert + if xt == nil || xv.IsNilInterface() { + // TODO: default panic type? + ex := fmt.Sprintf("interface conversion: interface is nil, not %s", t.String()) + m.Panic(typedString(ex)) + return + } + if it, ok := baseOf(t).(*InterfaceType); ok { + // An interface type assertion on a value that doesn't have a concrete base + // type should always fail. + if _, ok := baseOf(xt).(*InterfaceType); ok { + // TODO: default panic type? + ex := fmt.Sprintf( + "non-concrete %s doesn't implement %s", + xt.String(), + it.String()) + m.Panic(typedString(ex)) + return + } + // t is Gno interface. // assert that x implements type. - impl := false - impl = it.IsImplementedBy(xt) - if !impl { + err := it.VerifyImplementedBy(xt) + if err != nil { // TODO: default panic type? ex := fmt.Sprintf( - "%s doesn't implement %s", + "%s doesn't implement %s (%s)", xt.String(), - it.String()) + it.String(), + err.Error()) m.Panic(typedString(ex)) return } @@ -230,16 +255,22 @@ func (m *Machine) doOpTypeAssert1() { } else if nt, ok := baseOf(t).(*NativeType); ok { // t is Go interface. // assert that x implements type. - impl := false + errPrefix := "non-concrete " + var impl bool if nxt, ok := xt.(*NativeType); ok { - impl = nxt.Type.Implements(nt.Type) - } else { - impl = false + // If the underlying native type is reflect.Interface kind, then this has no + // concrete value and should fail. + if nxt.Type.Kind() != reflect.Interface { + impl = nxt.Type.Implements(nt.Type) + errPrefix = "" + } } + if !impl { // TODO: default panic type? ex := fmt.Sprintf( - "%s doesn't implement %s", + "%s%s doesn't implement %s", + errPrefix, xt.String(), nt.String()) m.Panic(typedString(ex)) @@ -251,6 +282,12 @@ func (m *Machine) doOpTypeAssert1() { panic("should not happen") } } else { // is concrete assert + if xt == nil { + ex := fmt.Sprintf("nil is not of type %s", t.String()) + m.Panic(typedString(ex)) + return + } + tid := t.TypeID() xtid := xt.TypeID() // assert that x is of type. @@ -273,17 +310,37 @@ func (m *Machine) doOpTypeAssert1() { func (m *Machine) doOpTypeAssert2() { m.PopExpr() // peek type for re-use - tv := m.PeekValue(1) - t := tv.GetType() + tv := m.PeekValue(1) // boolean result + t := tv.GetType() // type being asserted + // peek x for re-use - xv := m.PeekValue(2) - xt := xv.T + xv := m.PeekValue(2) // value result / value to assert + xt := xv.T // underlying value's type + + // xt may be nil, but we need to wait to return because the value of xt that is set + // will depend on whether we are trying to assert to an interface or concrete type. + // xt can be nil in the case where recover can't find a panic to recover from and + // returns a bare TypedValue{}. if t.Kind() == InterfaceKind { // is interface assert + if xt == nil { + *xv = TypedValue{} + *tv = untypedBool(false) + return + } + if it, ok := baseOf(t).(*InterfaceType); ok { + // An interface type assertion on a value that doesn't have a concrete base + // type should always fail. + if _, ok := baseOf(xt).(*InterfaceType); ok { + *xv = TypedValue{} + *tv = untypedBool(false) + return + } + // t is Gno interface. // assert that x implements type. - impl := false + var impl bool impl = it.IsImplementedBy(xt) if impl { // *xv = *xv @@ -295,14 +352,18 @@ func (m *Machine) doOpTypeAssert2() { *tv = untypedBool(false) } } else if nt, ok := baseOf(t).(*NativeType); ok { + // If the value being asserted on is nil, it can't implement an interface. // t is Go interface. // assert that x implements type. - impl := false + var impl bool if nxt, ok := xt.(*NativeType); ok { - impl = nxt.Type.Implements(nt.Type) - } else { - impl = false + // If the underlying native type is reflect.Interface kind, then this has no + // concrete value and should fail. + if nxt.Type.Kind() != reflect.Interface { + impl = nxt.Type.Implements(nt.Type) + } } + if impl { // *xv = *xv *tv = untypedBool(true) @@ -314,10 +375,20 @@ func (m *Machine) doOpTypeAssert2() { panic("should not happen") } } else { // is concrete assert + if xt == nil { + *xv = TypedValue{ + T: t, + V: defaultValue(m.Alloc, t), + } + *tv = untypedBool(false) + return + } + tid := t.TypeID() xtid := xt.TypeID() // assert that x is of type. same := tid == xtid + if same { // *xv = *xv *tv = untypedBool(true) diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 84c39716eec..7a8a885bcf0 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -26,6 +26,10 @@ func (m *Machine) doOpInc() { panic("expected lv.V to be nil for primitive type for OpInc") } } + + // here we can't just switch on the value type + // because it could be a type alias + // type num int switch baseOf(lv.T) { case IntType: lv.SetInt(lv.GetInt() + 1) diff --git a/gnovm/pkg/gnolang/ownership.go b/gnovm/pkg/gnolang/ownership.go index f2afc393d05..511b44bfc73 100644 --- a/gnovm/pkg/gnolang/ownership.go +++ b/gnovm/pkg/gnolang/ownership.go @@ -129,6 +129,7 @@ var ( _ Object = &BoundMethodValue{} _ Object = &MapValue{} _ Object = &Block{} + _ Object = &HeapItemValue{} ) type ObjectInfo struct { @@ -327,16 +328,7 @@ func (oi *ObjectInfo) GetIsTransient() bool { func (tv *TypedValue) GetFirstObject(store Store) Object { switch cv := tv.V.(type) { case PointerValue: - // TODO: in the future, consider skipping the base if persisted - // ref-count would be 1, e.g. only this pointer refers to - // something in it; in that case, ignore the base. That will - // likely require maybe a preparation step in persistence - // ( or unlikely, a second type of ref-counting). - if cv.Base != nil { - return cv.Base.(Object) - } else { - return cv.TV.GetFirstObject(store) - } + return cv.GetBase(store) case *ArrayValue: return cv case *SliceValue: @@ -359,6 +351,9 @@ func (tv *TypedValue) GetFirstObject(store Store) Object { oo := store.GetObject(cv.ObjectID) tv.V = oo return oo + case *HeapItemValue: + // should only appear in PointerValue.Base + panic("heap item value should only appear as a pointer's base") default: return nil } diff --git a/gnovm/pkg/gnolang/package.go b/gnovm/pkg/gnolang/package.go index 4d4cb5aaf45..e2fdb2580ca 100644 --- a/gnovm/pkg/gnolang/package.go +++ b/gnovm/pkg/gnolang/package.go @@ -31,6 +31,7 @@ var Package = amino.RegisterPackage(amino.NewPackage( // &NativeValue{}, &Block{}, RefValue{}, + &HeapItemValue{}, //---------------------------------------- // Realm/Object diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 12cf78cb7fa..d21e9bf0efd 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4,21 +4,32 @@ import ( "fmt" "math/big" "reflect" + "strings" "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" ) +const ( + blankIdentifier = "_" +) + // In the case of a *FileSet, some declaration steps have to happen // in a restricted parallel way across all the files. // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // First, initialize all file nodes and connect to package node. + // This will also reserve names on BlockNode.StaticBlock by + // calling StaticBlock.Predefine(). for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) - fn.InitStaticBlock(fn, pn) + initStaticBlocks(store, pn, fn) } + // NOTE: The calls to .Predefine() above is more of a name reservation, + // and what comes later in PredefineFileset() below is a second type of + // pre-defining mixed with defining, where recursive types are defined + // first and then filled out later. // NOTE: much of what follows is duplicated for a single *FileNode // in the main Preprocess translation function. Keep synced. @@ -95,6 +106,237 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } +// Initialize static block info. +func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) { + // create stack of BlockNodes. + var stack []BlockNode = make([]BlockNode, 0, 32) + var last BlockNode = ctx + stack = append(stack, last) + + // iterate over all nodes recursively. + _ = Transcribe(bn, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { + defer func() { + if r := recover(); r != nil { + // before re-throwing the error, append location information to message. + loc := last.GetLocation() + if nline := n.GetLine(); nline > 0 { + loc.Line = nline + } + + var err error + rerr, ok := r.(error) + if ok { + // NOTE: gotuna/gorilla expects error exceptions. + err = errors.Wrap(rerr, loc.String()) + } else { + // NOTE: gotuna/gorilla expects error exceptions. + err = fmt.Errorf("%s: %v", loc.String(), r) + } + + // Re-throw the error after wrapping it with the preprocessing stack information. + panic(&PreprocessError{ + err: err, + stack: stack, + }) + } + }() + + if debug { + debug.Printf("initStaticBlocks %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage) + } + + switch stage { + // ---------------------------------------- + case TRANS_ENTER: + switch n := n.(type) { + case *AssignStmt: + if n.Op == DEFINE { + var defined bool + for _, lx := range n.Lhs { + ln := lx.(*NameExpr).Name + if ln == blankIdentifier { + continue + } + last.Predefine(false, ln) + defined = true + } + if !defined { + panic(fmt.Sprintf("nothing defined in assignment %s", n.String())) + } + } + case *ImportDecl: + name := n.Name + if name == "." { + panic("dot imports not allowed in gno") + } + if name == "" { // use default + pv := store.GetPackage(n.PkgPath, true) + if pv == nil { + panic(fmt.Sprintf( + "unknown import path %s", + n.PkgPath)) + } + name = pv.PkgName + } + if name != blankIdentifier { + last.Predefine(false, name) + } + case *ValueDecl: + last2 := skipFile(last) + for i := 0; i < len(n.NameExprs); i++ { + nx := &n.NameExprs[i] + if nx.Name == blankIdentifier { + continue + } + last2.Predefine(n.Const, nx.Name) + } + case *TypeDecl: + last2 := skipFile(last) + last2.Predefine(false, n.Name) + case *FuncDecl: + if n.IsMethod { + if n.Recv.Name == "" || n.Recv.Name == blankIdentifier { + // create a hidden var with leading dot. + // NOTE: document somewhere. + n.Recv.Name = ".recv" + } + } else { + pkg := skipFile(last).(*PackageNode) + // special case: if n.Name == "init", assign unique suffix. + if n.Name == "init" { + idx := pkg.GetNumNames() + // NOTE: use a dot for init func suffixing. + // this also makes them unreferenceable. + dname := Name(fmt.Sprintf("init.%d", idx)) + n.Name = dname + } + + pkg.Predefine(false, n.Name) + } + case *FuncTypeExpr: + for i := range n.Params { + p := &n.Params[i] + if p.Name == "" || p.Name == blankIdentifier { + // create a hidden var with leading dot. + // NOTE: document somewhere. + pn := fmt.Sprintf(".arg_%d", i) + p.Name = Name(pn) + } + } + for i := range n.Results { + r := &n.Results[i] + if r.Name == blankIdentifier { + // create a hidden var with leading dot. + // NOTE: document somewhere. + rn := fmt.Sprintf(".res_%d", i) + r.Name = Name(rn) + } + } + } + return n, TRANS_CONTINUE + + // ---------------------------------------- + case TRANS_BLOCK: + switch n := n.(type) { + case *BlockStmt: + pushInitBlock(n, &last, &stack) + case *ForStmt: + pushInitBlock(n, &last, &stack) + case *IfStmt: + pushInitBlock(n, &last, &stack) + case *IfCaseStmt: + pushInitRealBlock(n, &last, &stack) + // parent if statement. + ifs := ns[len(ns)-1].(*IfStmt) + // anything declared in ifs are copied. + for _, n := range ifs.GetBlockNames() { + last.Predefine(false, n) + } + case *RangeStmt: + pushInitBlock(n, &last, &stack) + if n.Op == DEFINE { + if n.Key != nil { + last.Predefine(false, n.Key.(*NameExpr).Name) + } + if n.Value != nil { + last.Predefine(false, n.Value.(*NameExpr).Name) + } + } + case *FuncLitExpr: + pushInitBlock(n, &last, &stack) + for _, p := range n.Type.Params { + last.Predefine(false, p.Name) + } + for _, rf := range n.Type.Results { + if rf.Name != "" { + last.Predefine(false, rf.Name) + } + } + case *SelectCaseStmt: + pushInitBlock(n, &last, &stack) + case *SwitchStmt: + pushInitBlock(n, &last, &stack) + if n.VarName != "" { + // NOTE: this defines for default clauses too, + // see comment on block copying @ + // SwitchClauseStmt:TRANS_BLOCK. + last.Predefine(false, n.VarName) + } + case *SwitchClauseStmt: + pushInitRealBlock(n, &last, &stack) + // parent switch statement. + ss := ns[len(ns)-1].(*SwitchStmt) + // anything declared in ss are copied, + // namely ss.VarName if defined. + for _, n := range ss.GetBlockNames() { + last.Predefine(false, n) + } + if ss.IsTypeSwitch { + if ss.VarName != "" { + last.Predefine(false, ss.VarName) + } + } else { + if ss.VarName != "" { + panic("should not happen") + } + } + case *FuncDecl: + pushInitBlock(n, &last, &stack) + if n.IsMethod { + n.Predefine(false, n.Recv.Name) + } + for _, pte := range n.Type.Params { + if pte.Name == "" { + panic("should not happen") + } + n.Predefine(false, pte.Name) + } + for _, rte := range n.Type.Results { + if rte.Name != "" { + n.Predefine(false, rte.Name) + } + } + case *FileNode: + pushInitBlock(n, &last, &stack) + default: + panic("should not happen") + } + return n, TRANS_CONTINUE + + // ---------------------------------------- + case TRANS_LEAVE: + // finalization. + if _, ok := n.(BlockNode); ok { + // Pop block. + stack = stack[:len(stack)-1] + last = stack[len(stack)-1] + } + return n, TRANS_CONTINUE + } + return n, TRANS_CONTINUE + }) +} + // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. @@ -142,8 +384,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { lastpn := packageOf(last) stack = append(stack, last) - // iterate over all nodes recursively and calculate - // BlockValuePath for each NameExpr. + // iterate over all nodes recursively nn := Transcribe(n, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { // if already preprocessed, skip it. if n.GetAttribute(ATTR_PREPROCESSED) == true { @@ -156,6 +397,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { loc := last.GetLocation() if nline := n.GetLine(); nline > 0 { loc.Line = nline + loc.Column = n.GetColumn() } var err error @@ -186,25 +428,20 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_ENTER ----------------------- case *AssignStmt: if n.Op == DEFINE { - var defined bool for _, lx := range n.Lhs { ln := lx.(*NameExpr).Name - if ln == "_" { + if ln == blankIdentifier { // ignore. - } else { + } else if strings.HasPrefix(string(ln), ".decompose_") { _, ok := last.GetLocalIndex(ln) if !ok { // initial declaration to be re-defined. - last.Define(ln, anyValue(nil)) - defined = true + last.Predefine(false, ln) } else { // do not redeclare. } } } - if !defined { - panic(fmt.Sprintf("nothing defined in assignment %s", n.String())) - } } else { // nothing defined. } @@ -233,20 +470,14 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *FuncTypeExpr: for i := range n.Params { p := &n.Params[i] - if p.Name == "" || p.Name == "_" { - // create a hidden var with leading dot. - // NOTE: document somewhere. - pn := fmt.Sprintf(".arg_%d", i) - p.Name = Name(pn) + if p.Name == "" || p.Name == blankIdentifier { + panic("arg name should have been set in initStaticBlocks") } } for i := range n.Results { r := &n.Results[i] - if r.Name == "_" { - // create a hidden var with leading dot. - // NOTE: document somewhere. - rn := fmt.Sprintf(".res_%d", i) - r.Name = Name(rn) + if r.Name == blankIdentifier { + panic("result name should have been set in initStaticBlock") } } } @@ -276,12 +507,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_BLOCK ----------------------- case *IfCaseStmt: - pushRealBlock(n, &last, &stack) + pushInitRealBlockAndCopy(n, &last, &stack) // parent if statement. ifs := ns[len(ns)-1].(*IfStmt) // anything declared in ifs are copied. for _, n := range ifs.GetBlockNames() { - tv := ifs.GetValueRef(nil, n) + tv := ifs.GetValueRef(nil, n, false) last.Define(n, *tv) } @@ -392,13 +623,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_BLOCK ----------------------- case *SwitchClauseStmt: - pushRealBlock(n, &last, &stack) + pushInitRealBlockAndCopy(n, &last, &stack) // parent switch statement. ss := ns[len(ns)-1].(*SwitchStmt) // anything declared in ss are copied, // namely ss.VarName if defined. for _, n := range ss.GetBlockNames() { - tv := ss.GetValueRef(nil, n) + tv := ss.GetValueRef(nil, n, false) last.Define(n, *tv) } if ss.IsTypeSwitch { @@ -681,8 +912,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } // specific and general cases switch n.Name { - case "_": - n.Path = NewValuePathBlock(0, 0, "_") + case blankIdentifier: + n.Path = NewValuePathBlock(0, 0, blankIdentifier) return n, TRANS_CONTINUE case "iota": pd := lastDecl(ns) @@ -749,66 +980,75 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *BinaryExpr: lt := evalStaticTypeOf(store, last, n.Left) rt := evalStaticTypeOf(store, last, n.Right) - // Special (recursive) case if shift and right isn't uint. - isShift := n.Op == SHL || n.Op == SHR - if isShift && baseOf(rt) != UintType { - // convert n.Right to (gno) uint type, - rn := Expr(Call("uint", n.Right)) - // reset/create n2 to preprocess right child. - n2 := &BinaryExpr{ - Left: n.Left, - Op: n.Op, - Right: rn, - } - resn := Preprocess(store, last, n2) - return resn, TRANS_CONTINUE + + lcx, lic := n.Left.(*ConstExpr) + rcx, ric := n.Right.(*ConstExpr) + + if debug { + debug.Printf("Trans_leave, BinaryExpr, OP: %v, lx: %v, rx: %v, lt: %v, rt: %v, isLeftConstExpr: %v, isRightConstExpr %v, isLeftUntyped: %v, isRightUntyped: %v \n", n.Op, n.Left, n.Right, lt, rt, lic, ric, isUntyped(lt), isUntyped(rt)) } - // Left and right hand expressions must evaluate to a boolean typed value if - // the operation is a logical AND or OR. - if (n.Op == LAND || n.Op == LOR) && (lt.Kind() != BoolKind || rt.Kind() != BoolKind) { - panic("operands of boolean operators must evaluate to boolean typed values") + // Special (recursive) case if shift and right isn't uint. + isShift := n.Op == SHL || n.Op == SHR + if isShift { + // check LHS type compatibility + n.checkShiftLhs(lt) + // checkOrConvert RHS + if baseOf(rt) != UintType { + // convert n.Right to (gno) uint type, + rn := Expr(Call("uint", n.Right)) + // reset/create n2 to preprocess right child. + n2 := &BinaryExpr{ + Left: n.Left, + Op: n.Op, + Right: rn, + } + resn := Preprocess(store, last, n2) + return resn, TRANS_CONTINUE + } + // Then, evaluate the expression. + if lic && ric { + cx := evalConst(store, last, n) + return cx, TRANS_CONTINUE + } + return n, TRANS_CONTINUE } + // general cases + n.AssertCompatible(lt, rt) // check compatibility against binaryExprs other than shift expr // General case. - lcx, lic := n.Left.(*ConstExpr) - rcx, ric := n.Right.(*ConstExpr) if lic { if ric { // Left const, Right const ---------------------- // Replace with *ConstExpr if const operands. // First, convert untyped as necessary. - if !isShift { - cmp := cmpSpecificity(lcx.T, rcx.T) - if cmp < 0 { - // convert n.Left to right type. - checkOrConvertType(store, last, &n.Left, rcx.T, false) - } else if cmp == 0 { - // NOTE: the following doesn't work. - // TODO: make it work. - // convert n.Left to right type, - // or check for compatibility. - // (the other way around would work too) - // checkOrConvertType(store, last, n.Left, rcx.T, false) - } else { - // convert n.Right to left type. - checkOrConvertType(store, last, &n.Right, lcx.T, false) - } + if !shouldSwapOnSpecificity(lcx.T, rcx.T) { + // convert n.Left to right type. + checkOrConvertType(store, last, &n.Left, rcx.T, false) + } else { + // convert n.Right to left type. + checkOrConvertType(store, last, &n.Right, lcx.T, false) } // Then, evaluate the expression. cx := evalConst(store, last, n) return cx, TRANS_CONTINUE } else if isUntyped(lcx.T) { // Left untyped const, Right not ---------------- - if rnt, ok := rt.(*NativeType); ok { - if isShift { + if rnt, ok := rt.(*NativeType); ok { // untyped -> gno(native), e.g. 1*time.Second + if isShift { // RHS of shift should not be native panic("should not happen") } // get concrete native base type. - pt := go2GnoBaseType(rnt.Type).(PrimitiveType) + pt, ok := go2GnoBaseType(rnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + lt.String(), + rnt.String())) + } // convert n.Left to pt type, checkOrConvertType(store, last, &n.Left, pt, false) - // convert n.Right to (gno) pt type, + // if check pass, convert n.Right to (gno) pt type, rn := Expr(Call(pt.String(), n.Right)) // and convert result back. tx := constType(n, rnt) @@ -818,93 +1058,93 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Op: n.Op, Right: rn, } - resn := Node(Call(tx, n2)) + resn := Node(Call(tx, n2)) // this make current node to gonative{xxx} resn = Preprocess(store, last, resn) return resn, TRANS_CONTINUE // NOTE: binary operations are always computed in // gno, never with reflect. } else { - if isShift { - // nothing to do, right type is (already) uint type. - // we don't yet know what this type should be, - // but another checkOrConvertType() later does. - // (e.g. from AssignStmt or other). - } else { - // convert n.Left to right type. - checkOrConvertType(store, last, &n.Left, rt, false) - } + // convert n.Left to right type. + checkOrConvertType(store, last, &n.Left, rt, false) } - } else if lcx.T == nil { + } else if lcx.T == nil { // LHS is nil. // convert n.Left to typed-nil type. checkOrConvertType(store, last, &n.Left, rt, false) } - } else if ric { + } else if ric { // right is const, left is not if isUntyped(rcx.T) { // Left not, Right untyped const ---------------- - if isShift { - if baseOf(rt) != UintType { - // convert n.Right to (gno) uint type. - checkOrConvertType(store, last, &n.Right, UintType, false) - } else { - // leave n.Left as is and baseOf(n.Right) as UintType. + if lnt, ok := lt.(*NativeType); ok { + // get concrete native base type. + pt, ok := go2GnoBaseType(lnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + rt.String(), + lnt.String())) } - } else { - if lnt, ok := lt.(*NativeType); ok { - // get concrete native base type. - pt := go2GnoBaseType(lnt.Type).(PrimitiveType) - // convert n.Left to (gno) pt type, - ln := Expr(Call(pt.String(), n.Left)) - // convert n.Right to pt type, - checkOrConvertType(store, last, &n.Right, pt, false) - // and convert result back. - tx := constType(n, lnt) - // reset/create n2 to preprocess left child. - n2 := &BinaryExpr{ - Left: ln, - Op: n.Op, - Right: n.Right, - } - resn := Node(Call(tx, n2)) - resn = Preprocess(store, last, resn) - return resn, TRANS_CONTINUE - // NOTE: binary operations are always computed in - // gno, never with reflect. - } else { - // convert n.Right to left type. - checkOrConvertType(store, last, &n.Right, lt, false) + // convert n.Left to (gno) pt type, + ln := Expr(Call(pt.String(), n.Left)) + // convert n.Right to pt type, + checkOrConvertType(store, last, &n.Right, pt, false) + // and convert result back. + tx := constType(n, lnt) + // reset/create n2 to preprocess left child. + n2 := &BinaryExpr{ + Left: ln, + Op: n.Op, + Right: n.Right, } + resn := Node(Call(tx, n2)) + resn = Preprocess(store, last, resn) + return resn, TRANS_CONTINUE + // NOTE: binary operations are always computed in + // gno, never with reflect. + } else { + // convert n.Right to left type. + checkOrConvertType(store, last, &n.Right, lt, false) } - } else if rcx.T == nil { - // convert n.Right to typed-nil type. + } else if rcx.T == nil { // RHS is nil + // refer to tests/files/types/eql_0f20.gno checkOrConvertType(store, last, &n.Right, lt, false) } } else { // Left not const, Right not const ------------------ - if n.Op == EQL || n.Op == NEQ { - // If == or !=, no conversions. - } else if lnt, ok := lt.(*NativeType); ok { - if debug { - if !isShift { - assertSameTypes(lt, rt) - } - } + if lnt, ok := lt.(*NativeType); ok { // If left and right are native type, // convert left and right to gno, then // convert result back to native. - // - // get concrete native base type. - pt := go2GnoBaseType(lnt.Type).(PrimitiveType) + // get native base type. + lpt, ok := go2GnoBaseType(lnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + rt.String(), + lnt.String())) + } // convert n.Left to (gno) pt type, - ln := Expr(Call(pt.String(), n.Left)) - // convert n.Right to pt or uint type, + ln := Expr(Call(lpt.String(), n.Left)) + rn := n.Right - if isShift { - if baseOf(rt) != UintType { - rn = Expr(Call("uint", n.Right)) + // e.g. native: time.Second + time.Second, convert both(or it will be converted recursively) + // see tests/files/types/time_native.gno + if rnt, ok := rt.(*NativeType); ok { + rpt, ok := go2GnoBaseType(rnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + lt.String(), + rnt.String())) } - } else { - rn = Expr(Call(pt.String(), n.Right)) + // check assignable, if pass, convert right to gno first + assertAssignableTo(lpt, rpt, false) // both primitive types + rn = Expr(Call(rpt.String(), n.Right)) + } else { // rt not native + panic(fmt.Sprintf( + "incompatible operands in binary expression: %s %s %s", + lt.TypeID(), n.Op, rt.TypeID())) } + // and convert result back. tx := constType(n, lnt) // reset/create n2 to preprocess @@ -920,8 +1160,38 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: binary operations are always // computed in gno, never with // reflect. - } else if n.Op == SHL || n.Op == SHR { - // shift operator, nothing yet to do. + } else if rnt, ok := rt.(*NativeType); ok { // e.g. a * time.Second + pt, ok := go2GnoBaseType(rnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + lt.String(), + rnt.String())) + } + // convert n.Left to (gno) pt type, + rn := Expr(Call(pt.String(), n.Right)) + // convert n.Right to pt or uint type, + ln := n.Left + if isShift { + panic("should not happen") + } else { + checkOrConvertType(store, last, &n.Left, pt, false) + } + // and convert result back. + tx := constType(n, rnt) + // reset/create n2 to preprocess + // children. + n2 := &BinaryExpr{ + Left: ln, + Op: n.Op, + Right: rn, + } + resn := Node(Call(tx, n2)) + resn = Preprocess(store, last, resn) + return resn, TRANS_CONTINUE + // NOTE: binary operations are always + // computed in gno, never with + // reflect. } else { // non-shift non-const binary operator. liu, riu := isUntyped(lt), isUntyped(rt) @@ -930,26 +1200,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if lt.TypeID() != rt.TypeID() { panic(fmt.Sprintf( "incompatible types in binary expression: %v %v %v", - n.Left, n.Op, n.Right)) + lt.TypeID(), n.Op, rt.TypeID())) } - } else { + } else { // left untyped, right typed checkOrConvertType(store, last, &n.Left, rt, false) } - } else { - if riu { - checkOrConvertType(store, last, &n.Right, lt, false) + } else if riu { // left typed, right untyped + checkOrConvertType(store, last, &n.Right, lt, false) + } else { // both typed, refer to 0a1g.gno + if !shouldSwapOnSpecificity(lt, rt) { + checkOrConvertType(store, last, &n.Left, rt, false) } else { - // left is untyped, right is not. - if lt.TypeID() != rt.TypeID() { - panic(fmt.Sprintf( - "incompatible types in binary expression: %v %v %v", - n.Left, n.Op, n.Right)) - } + checkOrConvertType(store, last, &n.Right, lt, false) } } } } - // TRANS_LEAVE ----------------------- case *CallExpr: // Func type evaluation. @@ -1168,12 +1434,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { for i, tv := range argTVs { if hasVarg { if (len(spts) - 1) <= i { - checkType(tv.T, spts[len(spts)-1].Type.Elem(), true) + assertAssignableTo(tv.T, spts[len(spts)-1].Type.Elem(), true) } else { - checkType(tv.T, spts[i].Type, true) + assertAssignableTo(tv.T, spts[i].Type, true) } } else { - checkType(tv.T, spts[i].Type, true) + assertAssignableTo(tv.T, spts[i].Type, true) } } } else { @@ -1214,7 +1480,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case StringKind, ArrayKind, SliceKind: // Replace const index with int *ConstExpr, // or if not const, assert integer type.. - checkOrConvertIntegerType(store, last, n.Index) + checkOrConvertIntegerKind(store, last, n.Index) case MapKind: mt := baseOf(gnoTypeOf(store, dt)).(*MapType) checkOrConvertType(store, last, &n.Index, mt.Key, false) @@ -1228,22 +1494,48 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *SliceExpr: // Replace const L/H/M with int *ConstExpr, // or if not const, assert integer type.. - checkOrConvertIntegerType(store, last, n.Low) - checkOrConvertIntegerType(store, last, n.High) - checkOrConvertIntegerType(store, last, n.Max) + checkOrConvertIntegerKind(store, last, n.Low) + checkOrConvertIntegerKind(store, last, n.High) + checkOrConvertIntegerKind(store, last, n.Max) // TRANS_LEAVE ----------------------- case *TypeAssertExpr: if n.Type == nil { panic("should not happen") } + + // Type assertions on the blank identifier are illegal. + if nx, ok := n.X.(*NameExpr); ok && string(nx.Name) == blankIdentifier { + panic("cannot use _ as value or type") + } + // ExprStmt of form `x.()`, // or special case form `c, ok := x.()`. + t := evalStaticTypeOf(store, last, n.X) + baseType := baseOf(t) // The base type of the asserted value must be an interface. + switch bt := baseType.(type) { + case *InterfaceType: + break + case *NativeType: + if bt.Type.Kind() == reflect.Interface { + break + } + default: + panic( + fmt.Sprintf( + "invalid operation: %s (variable of type %s) is not an interface", + n.X.String(), + t.String(), + ), + ) + } + evalStaticType(store, last, n.Type) // TRANS_LEAVE ----------------------- case *UnaryExpr: xt := evalStaticTypeOf(store, last, n.X) + n.AssertCompatible(xt) if xnt, ok := xt.(*NativeType); ok { // get concrete native base type. pt := go2GnoBaseType(xnt.Type).(PrimitiveType) @@ -1291,12 +1583,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } case *ArrayType: for i := 0; i < len(n.Elts); i++ { - checkOrConvertType(store, last, &n.Elts[i].Key, IntType, false) + convertType(store, last, &n.Elts[i].Key, IntType) checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false) } case *SliceType: for i := 0; i < len(n.Elts); i++ { - checkOrConvertType(store, last, &n.Elts[i].Key, IntType, false) + convertType(store, last, &n.Elts[i].Key, IntType) checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false) } case *MapType: @@ -1537,6 +1829,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *AssignStmt: + n.AssertCompatible(store, last) // NOTE: keep DEFINE and ASSIGN in sync. if n.Op == DEFINE { // Rhs consts become default *ConstExprs. @@ -1544,22 +1837,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: does nothing if rx is "nil". convertIfConst(store, last, rx) } + if len(n.Lhs) > len(n.Rhs) { - // Unpack n.Rhs[0] to n.Lhs[:] - if len(n.Rhs) != 1 { - panic("should not happen") - } switch cx := n.Rhs[0].(type) { case *CallExpr: // Call case: a, b := x(...) ift := evalStaticTypeOf(store, last, cx.Func) cft := getGnoFuncTypeOf(store, ift) - if len(n.Lhs) != len(cft.Results) { - panic(fmt.Sprintf( - "assignment mismatch: "+ - "%d variables but %s returns %d values", - len(n.Lhs), cx.Func.String(), len(cft.Results))) - } for i, lx := range n.Lhs { ln := lx.(*NameExpr).Name rf := cft.Results[i] @@ -1567,11 +1851,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { last.Define(ln, anyValue(rf.Type)) } case *TypeAssertExpr: - // Type-assert case: a, ok := x.(type) - if len(n.Lhs) != 2 { - panic("should not happen") - } - cx.HasOK = true lhs0 := n.Lhs[0].(*NameExpr).Name lhs1 := n.Lhs[1].(*NameExpr).Name tt := evalStaticType(store, last, cx.Type) @@ -1579,16 +1858,15 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { last.Define(lhs0, anyValue(tt)) last.Define(lhs1, anyValue(BoolType)) case *IndexExpr: - // Index case: v, ok := x[k], x is map. - if len(n.Lhs) != 2 { - panic("should not happen") - } - cx.HasOK = true lhs0 := n.Lhs[0].(*NameExpr).Name lhs1 := n.Lhs[1].(*NameExpr).Name + var mt *MapType dt := evalStaticTypeOf(store, last, cx.X) - mt := baseOf(dt).(*MapType) + mt, ok := baseOf(dt).(*MapType) + if !ok { + panic(fmt.Sprintf("invalid index expression on %T", dt)) + } // re-definitions last.Define(lhs0, anyValue(mt.Value)) last.Define(lhs1, anyValue(BoolType)) @@ -1611,56 +1889,108 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.) - // If this is an assignment operation, ensure there's only 1 - // expr on lhs/rhs. - if n.Op != ASSIGN && - (len(n.Lhs) != 1 || len(n.Rhs) != 1) { - panic("assignment operator " + n.Op.TokenString() + - " requires only one expression on lhs and rhs") - } - // NOTE: Keep in sync with DEFINE above. - if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN { - // Special case if shift assign <<= or >>=. - checkOrConvertType(store, last, &n.Rhs[0], UintType, false) - } else if len(n.Lhs) > len(n.Rhs) { - // TODO dry code w/ above. - // Unpack n.Rhs[0] to n.Lhs[:] - if len(n.Rhs) != 1 { - panic("should not happen") - } - switch cx := n.Rhs[0].(type) { - case *CallExpr: + if len(n.Lhs) > len(n.Rhs) { + // check is done in assertCompatible where we also + // asserted we have at lease one element in Rhs + if cx, ok := n.Rhs[0].(*CallExpr); ok { + // we decompose the a,b = x(...) for named and unamed + // type value return in an assignments // Call case: a, b = x(...) ift := evalStaticTypeOf(store, last, cx.Func) cft := getGnoFuncTypeOf(store, ift) - if len(n.Lhs) != len(cft.Results) { - panic(fmt.Sprintf( - "assignment mismatch: "+ - "%d variables but %s returns %d values", - len(n.Lhs), cx.Func.String(), len(cft.Results))) + // check if we we need to decompose for named typed conversion in the function return results + var decompose bool + + for i, rhsType := range cft.Results { + lt := evalStaticTypeOf(store, last, n.Lhs[i]) + if lt != nil && isNamedConversion(rhsType.Type, lt) { + decompose = true + break + } } - case *TypeAssertExpr: - // Type-assert case: a, ok := x.(type) - if len(n.Lhs) != 2 { + if decompose { + // only enter this section if cft.Results to be converted to Lhs type for named type conversion. + // decompose a,b = x() + // .decompose1, .decompose2 := x() assignment statement expression (Op=DEFINE) + // a,b = .decompose1, .decompose2 assignment statement expression ( Op=ASSIGN ) + // add the new statement to last.Body + + // step1: + // create a hidden var with leading . (dot) the curBodyLen increase every time when there is a decomposition + // because there could be multiple decomposition happens + // we use both stmt index and return result number to differentiate the .decompose variables created in each assignment decompostion + // ex. .decompose_3_2: this variable is created as the 3rd statement in the block, the 2nd parameter returned from x(), + // create .decompose_1_1, .decompose_1_2 .... based on number of result from x() + tmpExprs := make(Exprs, 0, len(cft.Results)) + for i := range cft.Results { + rn := fmt.Sprintf(".decompose_%d_%d", index, i) + tmpExprs = append(tmpExprs, Nx(rn)) + } + // step2: + // .decompose1, .decompose2 := x() + dsx := &AssignStmt{ + Lhs: tmpExprs, + Op: DEFINE, + Rhs: n.Rhs, + } + dsx.SetLine(n.Line) + dsx = Preprocess(store, last, dsx).(*AssignStmt) + + // step3: + + // a,b = .decompose1, .decompose2 + // assign stmt expression + // The right-hand side will be converted to a call expression for named/unnamed conversion. + // tmpExprs is a []Expr; we make a copy of tmpExprs to prevent dsx.Lhs in the previous statement (dsx) from being changed by side effects. + // If we don't copy tmpExprs, when asx.Rhs is converted to a const call expression during the preprocessing of the AssignStmt asx, + // dsx.Lhs will change from []NameExpr to []CallExpr. + // This side effect would cause a panic when the machine executes the dsx statement, as it expects Lhs to be []NameExpr. + + asx := &AssignStmt{ + Lhs: n.Lhs, + Op: ASSIGN, + Rhs: copyExprs(tmpExprs), + } + asx.SetLine(n.Line) + asx = Preprocess(store, last, asx).(*AssignStmt) + + // step4: + // replace the original stmt with two new stmts + body := last.GetBody() + // we need to do an in-place replacement while leaving the current node + n.Attributes = dsx.Attributes + n.Lhs = dsx.Lhs + n.Op = dsx.Op + n.Rhs = dsx.Rhs + + // insert a assignment statement a,b = .decompose1,.decompose2 AFTER the current statement in the last.Body. + body = append(body[:index+1], append(Body{asx}, body[index+1:]...)...) + last.SetBody(body) + } // end of the decomposition + + // Last step: we need to insert the statements to FuncValue.body of PackageNopde.Values[i].V + // updating FuncValue.body=FuncValue.Source.Body in updates := pn.PrepareNewValues(pv) during preprocess. + // we updated FuncValue from source. + } + } else { // len(Lhs) == len(Rhs) + if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN { + if len(n.Lhs) != 1 || len(n.Rhs) != 1 { panic("should not happen") } - cx.HasOK = true - case *IndexExpr: - // Index case: v, ok := x[k], x is map. - if len(n.Lhs) != 2 { - panic("should not happen") + // Special case if shift assign <<= or >>=. + convertType(store, last, &n.Rhs[0], UintType) + } else if n.Op == ADD_ASSIGN || n.Op == SUB_ASSIGN || n.Op == MUL_ASSIGN || n.Op == QUO_ASSIGN || n.Op == REM_ASSIGN { + // e.g. a += b, single value for lhs and rhs, + lt := evalStaticTypeOf(store, last, n.Lhs[0]) + checkOrConvertType(store, last, &n.Rhs[0], lt, true) + } else { // all else, like BAND_ASSIGN, etc + // General case: a, b = x, y. + for i, lx := range n.Lhs { + lt := evalStaticTypeOf(store, last, lx) + // if lt is interface, nothing will happen + checkOrConvertType(store, last, &n.Rhs[i], lt, true) } - cx.HasOK = true - default: - panic("should not happen") - } - } else { - // General case: a, b = x, y. - for i, lx := range n.Lhs { - lt := evalStaticTypeOf(store, last, lx) - // converts if rx is "nil". - checkOrConvertType(store, last, &n.Rhs[i], lt, false) } } } @@ -1699,19 +2029,24 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { panic("should not happen") } + case *IncDecStmt: + xt := evalStaticTypeOf(store, last, n.X) + n.AssertCompatible(xt) + // TRANS_LEAVE ----------------------- case *ForStmt: // Cond consts become bool *ConstExprs. - checkOrConvertType(store, last, &n.Cond, BoolType, false) + checkOrConvertBoolKind(store, last, n.Cond) // TRANS_LEAVE ----------------------- case *IfStmt: // Cond consts become bool *ConstExprs. - checkOrConvertType(store, last, &n.Cond, BoolType, false) + checkOrConvertBoolKind(store, last, n.Cond) // TRANS_LEAVE ----------------------- case *RangeStmt: // NOTE: k,v already defined @ TRANS_BLOCK. + n.AssertCompatible(store, last) // TRANS_LEAVE ----------------------- case *ReturnStmt: @@ -1875,21 +2210,25 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } // evaluate typed value for static definition. - for i, vx := range n.Values { - if cx, ok := vx.(*ConstExpr); ok && - !cx.TypedValue.IsUndefined() { - if n.Const { - // const _ = : static block should contain value - tvs[i] = cx.TypedValue - } else { - // var _ = : static block should NOT contain value - tvs[i] = anyValue(cx.TypedValue.T) + for i := range n.NameExprs { + // consider value if specified. + if len(n.Values) > 0 { + vx := n.Values[i] + if cx, ok := vx.(*ConstExpr); ok && + !cx.TypedValue.IsUndefined() { + if n.Const { + // const _ = : static block should contain value + tvs[i] = cx.TypedValue + } else { + // var _ = : static block should NOT contain value + tvs[i] = anyValue(cx.TypedValue.T) + } + continue } - } else { - // for var decls of non-const expr. - st := sts[i] - tvs[i] = anyValue(st) } + // for var decls of non-const expr. + st := sts[i] + tvs[i] = anyValue(st) } } // define. @@ -1897,8 +2236,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { pn := fn.GetParentNode(nil).(*PackageNode) for i := 0; i < numNames; i++ { nx := &n.NameExprs[i] - if nx.Name == "_" { - nx.Path = NewValuePathBlock(0, 0, "_") + if nx.Name == blankIdentifier { + nx.Path = NewValuePathBlock(0, 0, blankIdentifier) } else { pn.Define2(n.Const, nx.Name, sts[i], tvs[i]) nx.Path = last.GetPathForName(nil, nx.Name) @@ -1907,8 +2246,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { for i := 0; i < numNames; i++ { nx := &n.NameExprs[i] - if nx.Name == "_" { - nx.Path = NewValuePathBlock(0, 0, "_") + if nx.Name == blankIdentifier { + nx.Path = NewValuePathBlock(0, 0, blankIdentifier) } else { last.Define2(n.Const, nx.Name, sts[i], tvs[i]) nx.Path = last.GetPathForName(nil, nx.Name) @@ -1926,7 +2265,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // during *TypeDecl:ENTER. Then, copy over the // values, completing the recursion. tmp := evalStaticType(store, last, n.Type) - dst := last.GetValueRef(store, n.Name).GetType() + dst := last.GetValueRef(store, n.Name, true).GetType() switch dst := dst.(type) { case *FuncType: *dst = *(tmp.(*FuncType)) @@ -1948,7 +2287,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { exists := false if dt := store.GetTypeSafe(tid); dt != nil { dst = dt.(*DeclaredType) - last.GetValueRef(store, n.Name).SetType(dst) + last.GetValueRef(store, n.Name, true).SetType(dst) exists = true } if !exists { @@ -1974,17 +2313,15 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { n.Type = constType(n.Type, dst) } // end type switch statement + // END TRANS_LEAVE ----------------------- - // TRANS_LEAVE ----------------------- - // finalization. + // finalization (during leave). if _, ok := n.(BlockNode); ok { // Pop block. stack = stack[:len(stack)-1] last = stack[len(stack)-1] - return n, TRANS_CONTINUE - } else { - return n, TRANS_CONTINUE } + return n, TRANS_CONTINUE } panic(fmt.Sprintf( @@ -2011,14 +2348,10 @@ func isSwitchLabel(ns []Node, label Name) bool { return false } +// Idempotent. func pushInitBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) { if !bn.IsInitialized() { bn.InitStaticBlock(bn, *last) - } else { - // This may happen when PredefineFileSet() followed by Preprocess(). - if _, ok := bn.(*FileNode); !ok { - panic("unexpected initialized block node type") - } } if bn.GetStaticBlock().Source != bn { panic("expected the source of a block node to be itself") @@ -2029,15 +2362,31 @@ func pushInitBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) { // like pushInitBlock(), but when the last block is a faux block, // namely after SwitchStmt and IfStmt. -func pushRealBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) { - orig := *last - // skip the faux block for parent of bn. - bn.InitStaticBlock(bn, (*last).GetParentNode(nil)) +// Idempotent. +func pushInitRealBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) { + if !bn.IsInitialized() { + bn.InitStaticBlock(bn, (*last).GetParentNode(nil)) + } + if bn.GetStaticBlock().Source != bn { + panic("expected the source of a block node to be itself") + } *last = bn *stack = append(*stack, bn) - // anything declared in orig are copied. +} + +// like pushInitBlock(), but when the last block is a faux block, +// namely after SwitchStmt and IfStmt. +// Not idempotent, as it calls bn.Define with reference to last's TV value slot. +func pushInitRealBlockAndCopy(bn BlockNode, last *BlockNode, stack *[]BlockNode) { + orig := *last + pushInitRealBlock(bn, last, stack) + copyFromFauxBlock(bn, orig) +} + +// anything declared in orig are copied. +func copyFromFauxBlock(bn BlockNode, orig BlockNode) { for _, n := range orig.GetBlockNames() { - tv := orig.GetValueRef(nil, n) + tv := orig.GetValueRef(nil, n, false) bn.Define(n, *tv) } } @@ -2229,12 +2578,12 @@ func getResultTypedValues(cx *CallExpr) []TypedValue { func evalConst(store Store, last BlockNode, x Expr) *ConstExpr { // TODO: some check or verification for ensuring x // is constant? From the machine? - cv := NewMachine(".dontcare", store) - tv := cv.EvalStatic(last, x) - cv.Release() + m := NewMachine(".dontcare", store) + cv := m.EvalStatic(last, x) + m.Release() cx := &ConstExpr{ Source: x, - TypedValue: tv, + TypedValue: cv, } cx.SetAttribute(ATTR_PREPROCESSED, true) setConstAttrs(cx) @@ -2400,45 +2749,25 @@ func isConstType(x Expr) bool { return ok } -func cmpSpecificity(t1, t2 Type) int { - t1s, t2s := 0, 0 - if t1p, ok := t1.(PrimitiveType); ok { - t1s = t1p.Specificity() - } - if t2p, ok := t2.(PrimitiveType); ok { - t2s = t2p.Specificity() - } - if t1s < t2s { - // NOTE: higher specificity has lower value, so backwards. - return 1 - } else if t1s == t2s { - return 0 - } else { - return -1 - } -} - -// 1. convert x to t if x is *ConstExpr. -// 2. otherwise, assert that x can be coerced to t. -// autoNative is usually false, but set to true -// for native function calls, where gno values are -// automatically converted to native go types. -// NOTE: also see checkOrConvertIntegerType() +// check before convert type func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative bool) { + if debug { + debug.Printf("checkOrConvertType, *x: %v:, t:%v \n", *x, t) + } if cx, ok := (*x).(*ConstExpr); ok { - convertConst(store, last, cx, t) + if _, ok := t.(*NativeType); !ok { // not native type, refer to time4_native.gno. + // e.g. int(1) == int8(1) + assertAssignableTo(cx.T, t, autoNative) + } } else if bx, ok := (*x).(*BinaryExpr); ok && (bx.Op == SHL || bx.Op == SHR) { - // "push" expected type into shift binary's left operand. + // "push" expected type into shift binary's left operand. recursively. checkOrConvertType(store, last, &bx.Left, t, autoNative) } else if *x != nil { // XXX if x != nil && t != nil { xt := evalStaticTypeOf(store, last, *x) if t != nil { - checkType(xt, t, autoNative) + assertAssignableTo(xt, t, autoNative) } if isUntyped(xt) { - if t == nil { - t = defaultTypeOf(xt) - } // Push type into expr if qualifying binary expr. if bx, ok := (*x).(*BinaryExpr); ok { switch bx.Op { @@ -2456,11 +2785,88 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative // default: } } - cx := Expr(Call(constType(nil, t), *x)) - cx = Preprocess(store, last, cx).(Expr) - *x = cx } } + // convert recursively + convertType(store, last, x, t) +} + +// 1. convert x to t if x is *ConstExpr. +// 2. otherwise, assert that x can be coerced to t. +// autoNative is usually false, but set to true +// for native function calls, where gno values are +// automatically converted to native go types. +// NOTE: also see checkOrConvertIntegerKind() +func convertType(store Store, last BlockNode, x *Expr, t Type) { + if debug { + debug.Printf("convertType, *x: %v:, t:%v \n", *x, t) + } + if cx, ok := (*x).(*ConstExpr); ok { + convertConst(store, last, cx, t) + } else if *x != nil { + xt := evalStaticTypeOf(store, last, *x) + if isUntyped(xt) { + if t == nil { + t = defaultTypeOf(xt) + } + if debug { + debug.Printf("default type of t: %v \n", t) + } + // convert x to destination type t + doConvertType(store, last, x, t) + } else { + // if one side is declared name type and the other side is unnamed type + if isNamedConversion(xt, t) { + // covert right (xt) to the type of the left (t) + doConvertType(store, last, x, t) + } + } + } +} + +// convert x to destination type t +func doConvertType(store Store, last BlockNode, x *Expr, t Type) { + cx := Expr(Call(constType(nil, t), *x)) + cx = Preprocess(store, last, cx).(Expr) + *x = cx +} + +// isNamedConversion returns true if assigning a value of type +// xt (rhs) into a value of type t (lhs) entails an implicit type conversion. +// xt is the result of an expression type. +// +// In a few special cases, we should not perform the conversion: +// +// case 1: the LHS is an interface, which is unnamed, so we should not +// convert to that even if right is a named type. +// case 2: isNamedConversion is called within evaluating make() or new() +// (uverse functions). It returns TypType (generic) which does have IsNamed appropriate +func isNamedConversion(xt, t Type) bool { + if t == nil { + t = xt + } + + // no conversion case 1: the LHS is an interface + + _, c1 := t.(*InterfaceType) + + // no conversion case2: isNamedConversion is called within evaluating make() or new() + // (uverse functions) + + _, oktt := t.(*TypeType) + _, oktt2 := xt.(*TypeType) + c2 := oktt || oktt2 + + // + if !c1 && !c2 { // carve out above two cases + // covert right to the type of left if one side is unnamed type and the other side is not + + if t.IsNamed() && !xt.IsNamed() || + !t.IsNamed() && xt.IsNamed() { + return true + } + } + return false } // like checkOrConvertType(last, x, nil) @@ -2473,7 +2879,7 @@ func convertIfConst(store Store, last BlockNode, x Expr) { func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { if t != nil && t.Kind() == InterfaceKind { if cx.T != nil { - checkType(cx.T, t, false) + assertAssignableTo(cx.T, t, false) } t = nil // signifies to convert to default type. } @@ -2487,232 +2893,6 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } -// Assert that xt can be assigned as dt (dest type). -// If autoNative is true, a broad range of xt can match against -// a target native dt type, if and only if dt is a native type. -func checkType(xt Type, dt Type, autoNative bool) { - // Special case if dt is interface kind: - if dt.Kind() == InterfaceKind { - if idt, ok := baseOf(dt).(*InterfaceType); ok { - if idt.IsEmptyInterface() { - // if dt is an empty Gno interface, any x ok. - return // ok - } else if idt.IsImplementedBy(xt) { - // if dt implements idt, ok. - return // ok - } else { - panic(fmt.Sprintf( - "%s does not implement %s", - xt.String(), - dt.String())) - } - } else if ndt, ok := baseOf(dt).(*NativeType); ok { - nidt := ndt.Type - if nidt.NumMethod() == 0 { - // if dt is an empty Go native interface, ditto. - return // ok - } else if nxt, ok := baseOf(xt).(*NativeType); ok { - // if xt has native base, do the naive native. - if nxt.Type.AssignableTo(nidt) { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use %s as %s", - nxt.String(), - nidt.String())) - } - } else if pxt, ok := baseOf(xt).(*PointerType); ok { - nxt, ok := pxt.Elt.(*NativeType) - if !ok { - panic(fmt.Sprintf( - "pointer to non-native type cannot satisfy non-empty native interface; %s doesn't implement %s", - pxt.String(), - nidt.String())) - } - // if xt has native base, do the naive native. - if reflect.PointerTo(nxt.Type).AssignableTo(nidt) { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use %s as %s", - pxt.String(), - nidt.String())) - } - } else { - panic(fmt.Sprintf( - "unexpected type pair: cannot use %s as %s", - xt.String(), - dt.String())) - } - } else { - panic("should not happen") - } - } - // Special case if xt or dt is *PointerType to *NativeType, - // convert to *NativeType of pointer kind. - if pxt, ok := xt.(*PointerType); ok { - // *gonative{x} is(to) gonative{*x} - //nolint:misspell - if enxt, ok := pxt.Elt.(*NativeType); ok { - xt = &NativeType{ - Type: reflect.PointerTo(enxt.Type), - } - } - } - if pdt, ok := dt.(*PointerType); ok { - // *gonative{x} is gonative{*x} - if endt, ok := pdt.Elt.(*NativeType); ok { - dt = &NativeType{ - Type: reflect.PointerTo(endt.Type), - } - } - } - // Special case of xt or dt is *DeclaredType, - // allow implicit conversion unless both are declared. - // TODO simplify with .IsNamedType(). - if dxt, ok := xt.(*DeclaredType); ok { - if ddt, ok := dt.(*DeclaredType); ok { - // types must match exactly. - if !dxt.sealed && !ddt.sealed && - dxt.PkgPath == ddt.PkgPath && - dxt.Name == ddt.Name { // not yet sealed - return // ok - } else if dxt.TypeID() == ddt.TypeID() { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use %s as %s without explicit conversion", - dxt.String(), - ddt.String())) - } - } else { - // special case if implicitly named primitive type. - // TODO simplify with .IsNamedType(). - if _, ok := dt.(PrimitiveType); ok { - panic(fmt.Sprintf( - "cannot use %s as %s without explicit conversion", - dxt.String(), - dt.String())) - } else { - // carry on with baseOf(dxt) - xt = dxt.Base - } - } - } else if ddt, ok := dt.(*DeclaredType); ok { - // special case if implicitly named primitive type. - // TODO simplify with .IsNamedType(). - if _, ok := xt.(PrimitiveType); ok { - panic(fmt.Sprintf( - "cannot use %s as %s without explicit conversion", - xt.String(), - ddt.String())) - } else { - // carry on with baseOf(ddt) - dt = ddt.Base - } - } - // General cases. - switch cdt := dt.(type) { - case PrimitiveType: - // if xt is untyped, ensure dt is compatible. - switch xt { - case UntypedBoolType: - if dt.Kind() == BoolKind { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use untyped bool as %s", - dt.Kind())) - } - case UntypedStringType: - if dt.Kind() == StringKind { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use untyped string as %s", - dt.Kind())) - } - case UntypedRuneType, UntypedBigintType: - switch dt.Kind() { - case IntKind, Int8Kind, Int16Kind, Int32Kind, - Int64Kind, UintKind, Uint8Kind, Uint16Kind, - Uint32Kind, Uint64Kind: - return // ok - default: - panic(fmt.Sprintf( - "cannot use untyped rune as %s", - dt.Kind())) - } - default: - if isUntyped(xt) { - panic("unexpected untyped type") - } - if xt.TypeID() == cdt.TypeID() { - return // ok - } - } - case *PointerType: - if pt, ok := xt.(*PointerType); ok { - checkType(pt.Elt, cdt.Elt, false) - return // ok - } - case *ArrayType: - if at, ok := xt.(*ArrayType); ok { - checkType(at.Elt, cdt.Elt, false) - return // ok - } - case *SliceType: - if st, ok := xt.(*SliceType); ok { - checkType(st.Elt, cdt.Elt, false) - return // ok - } - case *MapType: - if mt, ok := xt.(*MapType); ok { - checkType(mt.Key, cdt.Key, false) - checkType(mt.Value, cdt.Value, false) - return // ok - } - case *FuncType: - if xt.TypeID() == cdt.TypeID() { - return // ok - } - case *InterfaceType: - panic("should not happen") - case *DeclaredType: - panic("should not happen") - case *StructType, *PackageType, *ChanType: - if xt.TypeID() == cdt.TypeID() { - return // ok - } - case *TypeType: - if xt.TypeID() == cdt.TypeID() { - return // ok - } - case *NativeType: - if !autoNative { - if xt.TypeID() == cdt.TypeID() { - return // ok - } - } else { - // autoNative, so check whether matches. - // xt: any type but a *DeclaredType; could be native. - // cdt: actual concrete native target type. - // ie, if cdt can match against xt. - if gno2GoTypeMatches(xt, cdt.Type) { - return // ok - } - } - default: - panic(fmt.Sprintf( - "unexpected type %s", - dt.String())) - } - panic(fmt.Sprintf( - "cannot use %s as %s", - xt.String(), - dt.String())) -} - // Returns any names not yet defined nor predefined in expr. These happen // upon transcribe:enter from the top, so value paths cannot be used. If no // names are un and x is TypeExpr, evalStaticType(store,last, x) must not @@ -2731,7 +2911,7 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { } switch cx := x.(type) { case *NameExpr: - if tv := last.GetValueRef(store, cx.Name); tv != nil { + if tv := last.GetValueRef(store, cx.Name, true); tv != nil { return } if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { @@ -2925,18 +3105,40 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { return } -// like checkOrConvertType() but for any integer type. -func checkOrConvertIntegerType(store Store, last BlockNode, x Expr) { +// like checkOrConvertType() but for any typed bool kind. +func checkOrConvertBoolKind(store Store, last BlockNode, x Expr) { + if cx, ok := x.(*ConstExpr); ok { + convertConst(store, last, cx, BoolType) + } else if x != nil { + xt := evalStaticTypeOf(store, last, x) + checkBoolKind(xt) + } +} + +// assert that xt is a typed bool kind. +func checkBoolKind(xt Type) { + switch xt.Kind() { + case BoolKind: + return // ok + default: + panic(fmt.Sprintf( + "expected typed bool kind, but got %v", + xt.Kind())) + } +} + +// like checkOrConvertType() but for any typed integer kind. +func checkOrConvertIntegerKind(store Store, last BlockNode, x Expr) { if cx, ok := x.(*ConstExpr); ok { convertConst(store, last, cx, IntType) } else if x != nil { xt := evalStaticTypeOf(store, last, x) - checkIntegerType(xt) + checkIntegerKind(xt) } } -// assert that xt can be assigned as an integer type. -func checkIntegerType(xt Type) { +// assert that xt is a typed integer kind. +func checkIntegerKind(xt Type) { switch xt.Kind() { case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind, UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind, @@ -2944,7 +3146,7 @@ func checkIntegerType(xt Type) { return // ok default: panic(fmt.Sprintf( - "expected integer type, but got %v", + "expected typed integer kind, but got %v", xt.Kind())) } } @@ -2971,6 +3173,7 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { loc := last.GetLocation() if nline := d.GetLine(); nline > 0 { loc.Line = nline + loc.Column = d.GetColumn() } if rerr, ok := r.(error); ok { // NOTE: gotuna/gorilla expects error exceptions. @@ -3022,10 +3225,8 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // NOTE: unlike the *ValueDecl case, this case doesn't // preprocess d itself (only d.Type). if cd.IsMethod { - if cd.Recv.Name == "" || cd.Recv.Name == "_" { - // create a hidden var with leading dot. - // NOTE: document somewhere. - cd.Recv.Name = ".recv" + if cd.Recv.Name == "" || cd.Recv.Name == blankIdentifier { + panic("cd.Recv.Name should have been set in initStaticBlocks") } cd.Recv = *Preprocess(store, last, &cd.Recv).(*FieldTypeExpr) cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) @@ -3059,7 +3260,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } else { panic("should not happen") } - + // The body may get altered during preprocessing later. if !dt.TryDefineMethod(&FuncValue{ Type: ft, IsMethod: true, @@ -3078,7 +3279,10 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De dt.Name, cd.Name)) } } else { - ftv := pkg.GetValueRef(store, cd.Name) + if cd.Name == "init" { + panic("cd.Name 'init' should have been appended with a number in initStaticBlocks") + } + ftv := pkg.GetValueRef(store, cd.Name, true) ft := ftv.T.(*FuncType) cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) ft2 := evalStaticType(store, last, &cd.Type).(*FuncType) @@ -3119,7 +3323,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // non-package) stmt bodies. func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { if d.GetAttribute(ATTR_PREDEFINED) == true { - panic("decl node already predefined!") + panic(fmt.Sprintf("decl node already predefined! %v", d)) } // If un is blank, it means the predefine succeeded. @@ -3141,7 +3345,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } if d.Name == "" { // use default d.Name = pv.PkgName - } else if d.Name == "_" { // no definition + } else if d.Name == blankIdentifier { // no definition return } else if d.Name == "." { // dot import panic("dot imports not allowed in Gno") @@ -3168,19 +3372,16 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } for i := 0; i < len(d.NameExprs); i++ { nx := &d.NameExprs[i] - if nx.Name == "_" { - nx.Path.Name = "_" + if nx.Name == blankIdentifier { + nx.Path.Name = blankIdentifier } else { - last2 := skipFile(last) - last2.Predefine(d.Const, nx.Name) nx.Path = last.GetPathForName(store, nx.Name) } } case *TypeDecl: // before looking for dependencies, predefine empty type. last2 := skipFile(last) - _, ok := last2.GetLocalIndex(d.Name) - if !ok { + if !isLocallyDefined(last2, d.Name) { // construct empty t type var t Type switch tx := d.Type.(type) { @@ -3201,8 +3402,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { case *StarExpr: t = &PointerType{} case *NameExpr: - if tv := last.GetValueRef(store, tx.Name); tv != nil { - // (file) block name + if tv := last.GetValueRef(store, tx.Name, true); tv != nil { t = tv.GetType() if dt, ok := t.(*DeclaredType); ok { if !dt.sealed { @@ -3230,7 +3430,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return } pkgName := tx.X.(*NameExpr).Name - tv := last.GetValueRef(store, pkgName) + tv := last.GetValueRef(store, pkgName, true) pv, ok := tv.V.(*PackageValue) if !ok { panic(fmt.Sprintf( @@ -3243,7 +3443,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { pn := pv.GetPackageNode(store) tx.Path = pn.GetPathForName(store, tx.Sel) ptr := pv.GetBlock(store).GetPointerTo(store, tx.Path) - t = ptr.TV.T + t = ptr.TV.GetType() default: panic(fmt.Sprintf( "unexpected type declaration type %v", @@ -3280,19 +3480,15 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return } } else { + if d.Name == "init" { + panic("cd.Name 'init' should have been appended with a number in initStaticBlocks") + } // define package-level function. ft := &FuncType{} pkg := skipFile(last).(*PackageNode) - // special case: if d.Name == "init", assign unique suffix. - if d.Name == "init" { - idx := pkg.GetNumNames() - // NOTE: use a dot for init func suffixing. - // this also makes them unreferenceable. - dname := Name(fmt.Sprintf("init.%d", idx)) - d.Name = dname - } // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. + // The body may get altered during preprocessing later. fv := &FuncValue{ Type: ft, IsMethod: false, @@ -3349,7 +3545,7 @@ func constUntypedBigint(source Expr, i64 int64) *ConstExpr { } func fillNameExprPath(last BlockNode, nx *NameExpr, isDefineLHS bool) { - if nx.Name == "_" { + if nx.Name == blankIdentifier { // Blank name has no path; caller error. panic("should not happen") } @@ -3673,6 +3869,23 @@ func findDependentNames(n Node, dst map[Name]struct{}) { } } +// A name is locally defined on a block node +// if the type is set to anything but nil. +// A predefined name will return false. +// NOTE: the value is not necessarily set statically, +// unless it refers to a type, package, or statically declared func value. +func isLocallyDefined(bn BlockNode, n Name) bool { + idx, ok := bn.GetLocalIndex(n) + if !ok { + return false + } + t := bn.GetStaticBlock().Types[idx] + if t == nil { + return false + } + return true +} + // ---------------------------------------- // SetNodeLocations @@ -3686,26 +3899,17 @@ func SetNodeLocations(pkgPath string, fileName string, n Node) { if pkgPath == "" || fileName == "" { panic("missing package path or file name") } - lastLine := 0 - nextNonce := 0 Transcribe(n, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { if stage != TRANS_ENTER { return n, TRANS_CONTINUE } if bn, ok := n.(BlockNode); ok { // ensure unique location of blocknode. - line := bn.GetLine() - if line == lastLine { - nextNonce += 1 - } else { - lastLine = line - nextNonce = 0 - } loc := Location{ PkgPath: pkgPath, File: fileName, - Line: line, - Nonce: nextNonce, + Line: bn.GetLine(), + Column: bn.GetColumn(), } bn.SetLocation(loc) } @@ -3747,6 +3951,9 @@ func SaveBlockNodes(store Store, fn *FileNode) { if loc.Line != bn.GetLine() { panic("wrong line in block node location") } + if loc.Column != bn.GetColumn() { + panic("wrong column in block node location") + } // save blocknode. store.SetBlockNode(bn) } diff --git a/gnovm/pkg/gnolang/preprocess_test.go b/gnovm/pkg/gnolang/preprocess_test.go new file mode 100644 index 00000000000..73e1318b062 --- /dev/null +++ b/gnovm/pkg/gnolang/preprocess_test.go @@ -0,0 +1,62 @@ +package gnolang + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestPreprocess_BinaryExpressionOneNative(t *testing.T) { + pn := NewPackageNode("time", "time", nil) + pn.DefineGoNativeValue("Millisecond", time.Millisecond) + pn.DefineGoNativeValue("Second", time.Second) + pn.DefineGoNativeType(reflect.TypeOf(time.Duration(0))) + pv := pn.NewPackage() + store := gonativeTestStore(pn, pv) + store.SetBlockNode(pn) + + const src = `package main + import "time" +func main() { + var a int64 = 2 + println(time.Second * a) + +}` + n := MustParseFile("main.go", src) + + defer func() { + err := recover() + assert.Contains(t, fmt.Sprint(err), "incompatible operands in binary expression") + }() + initStaticBlocks(store, pn, n) + Preprocess(store, pn, n) +} + +func TestPreprocess_BinaryExpressionBothNative(t *testing.T) { + pn := NewPackageNode("time", "time", nil) + pn.DefineGoNativeValue("March", time.March) + pn.DefineGoNativeValue("Wednesday", time.Wednesday) + pn.DefineGoNativeType(reflect.TypeOf(time.Month(0))) + pn.DefineGoNativeType(reflect.TypeOf(time.Weekday(0))) + pv := pn.NewPackage() + store := gonativeTestStore(pn, pv) + store.SetBlockNode(pn) + + const src = `package main + import "time" +func main() { + println(time.March * time.Wednesday) + +}` + n := MustParseFile("main.go", src) + + defer func() { + err := recover() + assert.Contains(t, fmt.Sprint(err), "incompatible operands in binary expression") + }() + initStaticBlocks(store, pn, n) + Preprocess(store, pn, n) +} diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 3f615772426..3710524130a 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -73,9 +73,16 @@ func PkgIDFromPkgPath(path string) PkgID { return PkgID{HashBytes([]byte(path))} } +// Returns the ObjectID of the PackageValue associated with path. func ObjectIDFromPkgPath(path string) ObjectID { + pkgID := PkgIDFromPkgPath(path) + return ObjectIDFromPkgID(pkgID) +} + +// Returns the ObjectID of the PackageValue associated with pkgID. +func ObjectIDFromPkgID(pkgID PkgID) ObjectID { return ObjectID{ - PkgID: PkgIDFromPkgPath(path), + PkgID: pkgID, NewTime: 1, // by realm logic. } } @@ -147,6 +154,12 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if po.GetObjectID().PkgID != rlm.ID { panic("cannot modify external-realm or non-realm object") } + + // XXX check if this boosts performance + // XXX with broad integration benchmarking. + // XXX if co == xo { + // XXX } + // From here on, po is real (not new-real). // Updates to .newCreated/.newEscaped /.newDeleted made here. (first gen) // More appends happen during FinalizeRealmTransactions(). (second+ gen) @@ -723,18 +736,6 @@ func (rlm *Realm) saveObject(store Store, oo Object) { if oid.IsZero() { panic("unexpected zero object id") } - /* XXX DELETE - // ensure all types were already saved (@ preprocessor). - if debug { - types := getUnsavedTypes(oo, nil) - for _, typ := range types { - tid := typ.TypeID() - if store.GetType(tid) == nil { - panic("missing type") - } - } - } - */ // set hash to escape index. if oo.GetIsNewEscaped() { oo.SetIsNewEscaped(false) @@ -822,11 +823,10 @@ func getChildObjects(val Value, more []Value) []Value { case DataByteValue: panic("cannot get children from data byte objects") case PointerValue: - if cv.Base != nil { - more = getSelfOrChildObjects(cv.Base, more) - } else { - more = getSelfOrChildObjects(cv.TV.V, more) + if cv.Base == nil { + panic("should not happen") } + more = getSelfOrChildObjects(cv.Base, more) return more case *ArrayValue: for _, ctv := range cv.List { @@ -868,8 +868,14 @@ func getChildObjects(val Value, more []Value) []Value { for _, ctv := range cv.Values { more = getSelfOrChildObjects(ctv.V, more) } + // Generally the parent block must also be persisted. + // Otherwise NamePath may not resolve when referencing + // a parent block. more = getSelfOrChildObjects(cv.Parent, more) return more + case *HeapItemValue: + more = getSelfOrChildObjects(cv.Value.V, more) + return more case *NativeValue: panic("native values not supported") default: @@ -935,7 +941,7 @@ func copyMethods(methods []TypedValue) []TypedValue { // gets saved (e.g. from *Machine.savePackage()). res[i] = TypedValue{ T: copyTypeWithRefs(mtv.T), - V: copyValueWithRefs(nil, mtv.V), + V: copyValueWithRefs(mtv.V), } } return res @@ -1054,7 +1060,7 @@ func copyTypeWithRefs(typ Type) Type { // persistence bytes serialization. // Also checks for integrity of immediate children -- they must already be // persistent (real), and not dirty, or else this function panics. -func copyValueWithRefs(parent Object, val Value) Value { +func copyValueWithRefs(val Value) Value { switch cv := val.(type) { case nil: return nil @@ -1067,33 +1073,25 @@ func copyValueWithRefs(parent Object, val Value) Value { case DataByteValue: panic("cannot copy data byte value with references") case PointerValue: - if cv.Base != nil { - return PointerValue{ - /* - already represented in .Base[Index]: - TypedValue: &TypedValue{ - T: cv.TypedValue.T, - V: copyValueWithRefs(cv.TypedValue.V), - }, - */ - Base: toRefValue(parent, cv.Base), - Index: cv.Index, - } - } else { - etv := refOrCopyValue(parent, *cv.TV) - return PointerValue{ - TV: &etv, - /* - Base: nil, - Index: 0, - */ - } + if cv.Base == nil { + panic("should not happen") + } + return PointerValue{ + /* + already represented in .Base[Index]: + TypedValue: &TypedValue{ + T: cv.TypedValue.T, + V: copyValueWithRefs(cv.TypedValue.V), + }, + */ + Base: toRefValue(cv.Base), + Index: cv.Index, } case *ArrayValue: if cv.Data == nil { list := make([]TypedValue, len(cv.List)) for i, etv := range cv.List { - list[i] = refOrCopyValue(cv, etv) + list[i] = refOrCopyValue(etv) } return &ArrayValue{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1107,7 +1105,7 @@ func copyValueWithRefs(parent Object, val Value) Value { } case *SliceValue: return &SliceValue{ - Base: toRefValue(parent, cv.Base), + Base: toRefValue(cv.Base), Offset: cv.Offset, Length: cv.Length, Maxcap: cv.Maxcap, @@ -1115,7 +1113,7 @@ func copyValueWithRefs(parent Object, val Value) Value { case *StructValue: fields := make([]TypedValue, len(cv.Fields)) for i, ftv := range cv.Fields { - fields[i] = refOrCopyValue(cv, ftv) + fields[i] = refOrCopyValue(ftv) } return &StructValue{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1129,7 +1127,7 @@ func copyValueWithRefs(parent Object, val Value) Value { } var closure Value if cv.Closure != nil { - closure = toRefValue(parent, cv.Closure) + closure = toRefValue(cv.Closure) } // nativeBody funcs which don't come from NativeStore (and thus don't // have NativePkg/Name) can't be persisted, and should not be able @@ -1150,8 +1148,8 @@ func copyValueWithRefs(parent Object, val Value) Value { NativeName: cv.NativeName, } case *BoundMethodValue: - fnc := copyValueWithRefs(cv, cv.Func).(*FuncValue) - rtv := refOrCopyValue(cv, cv.Receiver) + fnc := copyValueWithRefs(cv.Func).(*FuncValue) + rtv := refOrCopyValue(cv.Receiver) return &BoundMethodValue{ ObjectInfo: cv.ObjectInfo.Copy(), // XXX ??? Func: fnc, @@ -1160,8 +1158,8 @@ func copyValueWithRefs(parent Object, val Value) Value { case *MapValue: list := &MapList{} for cur := cv.List.Head; cur != nil; cur = cur.Next { - key2 := refOrCopyValue(cv, cur.Key) - val2 := refOrCopyValue(cv, cur.Value) + key2 := refOrCopyValue(cur.Key) + val2 := refOrCopyValue(cur.Value) list.Append(nilAllocator, key2).Value = val2 } return &MapValue{ @@ -1171,10 +1169,10 @@ func copyValueWithRefs(parent Object, val Value) Value { case TypeValue: return toTypeValue(copyTypeWithRefs(cv.Type)) case *PackageValue: - block := toRefValue(cv, cv.Block) + block := toRefValue(cv.Block) fblocks := make([]Value, len(cv.FBlocks)) for i, fb := range cv.FBlocks { - fblocks[i] = toRefValue(cv, fb) + fblocks[i] = toRefValue(fb) } return &PackageValue{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1189,11 +1187,11 @@ func copyValueWithRefs(parent Object, val Value) Value { source := toRefNode(cv.Source) vals := make([]TypedValue, len(cv.Values)) for i, tv := range cv.Values { - vals[i] = refOrCopyValue(cv, tv) + vals[i] = refOrCopyValue(tv) } var bparent Value if cv.Parent != nil { - bparent = toRefValue(parent, cv.Parent) + bparent = toRefValue(cv.Parent) } bl := &Block{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1205,6 +1203,24 @@ func copyValueWithRefs(parent Object, val Value) Value { return bl case RefValue: return cv + case *HeapItemValue: + // NOTE: While this could be eliminated sometimes with some + // intelligence prior to persistence, to unwrap the + // HeapItemValue in case where the HeapItemValue only has + // refcount of 1, + // + // 1. The HeapItemValue is necessary when the .Value is a + // primitive non-object anyways, and + // 2. This would mean PointerValue.Base is nil, and we'd need + // additional logic to re-wrap when necessary, and + // 3. And with the above point, it's not clear the result + // would be any faster. But this is something we could + // explore after launch. + hiv := &HeapItemValue{ + ObjectInfo: cv.ObjectInfo.Copy(), + Value: refOrCopyValue(cv.Value), + } + return hiv case *NativeValue: panic("native values not supported") default: @@ -1376,6 +1392,9 @@ func fillTypesOfValue(store Store, val Value) Value { panic("native values not supported") case RefValue: // do nothing return cv + case *HeapItemValue: + fillTypesTV(store, &cv.Value) + return cv default: panic(fmt.Sprintf( "unexpected type %v", @@ -1423,7 +1442,7 @@ func toRefNode(bn BlockNode) RefNode { } } -func toRefValue(parent Object, val Value) RefValue { +func toRefValue(val Value) RefValue { // TODO use type switch stmt. if ref, ok := val.(RefValue); ok { return ref @@ -1496,15 +1515,15 @@ func ensureUniq(oozz ...[]Object) { } } -func refOrCopyValue(parent Object, tv TypedValue) TypedValue { +func refOrCopyValue(tv TypedValue) TypedValue { if tv.T != nil { tv.T = refOrCopyType(tv.T) } if obj, ok := tv.V.(Object); ok { - tv.V = toRefValue(parent, obj) + tv.V = toRefValue(obj) return tv } else { - tv.V = copyValueWithRefs(parent, tv.V) + tv.V = copyValueWithRefs(tv.V) return tv } } @@ -1513,16 +1532,6 @@ func isUnsaved(oo Object) bool { return oo.GetIsNewReal() || oo.GetIsDirty() } -// realmPathPrefix is the prefix used to identify pkgpaths which are meant to -// be realms and as such to have their state persisted. This is used by [IsRealmPath]. -const realmPathPrefix = "gno.land/r/" - -// IsRealmPath determines whether the given pkgpath is for a realm, and as such -// should persist the global state. -func IsRealmPath(pkgPath string) bool { - return strings.HasPrefix(pkgPath, realmPathPrefix) -} - func prettyJSON(jstr []byte) []byte { var c interface{} err := json.Unmarshal(jstr, &c) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 3db53213f8b..038f4ba894b 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -9,8 +9,12 @@ import ( "strings" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/colors" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" + "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/store/utils" + stringz "github.com/gnolang/gno/tm2/pkg/strings" ) // PackageGetter specifies how the store may retrieve packages which are not @@ -18,7 +22,8 @@ import ( // package does not exist. store should be used to run the machine, or otherwise // call any methods which may call store.GetPackage; avoid using any "global" // store as the one passed to the PackageGetter may be a fork of that (ie. -// the original is not meant to be written to). +// the original is not meant to be written to). Loading dependencies may +// cause writes to happen to the store, such as MemPackages to iavlstore. type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) @@ -68,6 +73,8 @@ type Store interface { LogSwitchRealm(rlmpath string) // to mark change of realm boundaries ClearCache() Print() + Write() + Flush() } // Used to keep track of in-mem objects during tx. @@ -86,7 +93,7 @@ type defaultStore struct { // transient opslog []StoreOp // for debugging and testing. - current []string + current []string // for detecting import cycles. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -105,6 +112,16 @@ func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore return ds } +// CopyCachesFromStore allows to copy a store's internal object, type and +// BlockNode cache into the dst store. +// This is mostly useful for testing, where many stores have to be initialized. +func CopyCachesFromStore(dst, src Store) { + ds, ss := dst.(*defaultStore), src.(*defaultStore) + ds.cacheObjects = maps.Clone(ss.cacheObjects) + ds.cacheTypes = maps.Clone(ss.cacheTypes) + ds.cacheNodes = maps.Clone(ss.cacheNodes) +} + func (ds *defaultStore) GetAllocator() *Allocator { return ds.alloc } @@ -115,6 +132,7 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + // helper to detect circular imports if isImport { if slices.Contains(ds.current, pkgPath) { panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) @@ -196,7 +214,10 @@ func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue // but packages gotten from the pkgGetter may skip this step, // so fill in store.CacheTypes here. for _, tv := range pv.GetBlock(nil).Values { - if tv.T.Kind() == TypeKind { + if tv.T == nil { + // tv.T is nil here only when only predefined. + // (for other types, .T == nil even after definition). + } else if tv.T.Kind() == TypeKind { t := tv.GetType() ds.SetCacheType(t) } @@ -305,7 +326,7 @@ func (ds *defaultStore) loadObjectSafe(oid ObjectID) Object { func (ds *defaultStore) SetObject(oo Object) { oid := oo.GetObjectID() // replace children/fields with Ref. - o2 := copyValueWithRefs(nil, oo) + o2 := copyValueWithRefs(oo) // marshal to binary. bz := amino.MustMarshalAny(o2) // set hash. @@ -554,7 +575,7 @@ func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage // implementations works by running Machine.RunMemPackage with save = true, // which would add the package to the store after running. // Some packages may never be persisted, thus why we only attempt this twice. - if !isRetry { + if !isRetry && ds.pkgGetter != nil { if pv := ds.GetPackage(path, false); pv != nil { return ds.getMemPackage(path, true) } @@ -672,8 +693,16 @@ func (ds *defaultStore) GetNative(pkgPath string, name Name) func(m *Machine) { return nil } +// Writes one level of cache to store. +func (ds *defaultStore) Write() { + ds.baseStore.(types.Writer).Write() + ds.iavlStore.(types.Writer).Write() +} + +// Flush cached writes to disk. func (ds *defaultStore) Flush() { - // XXX + ds.baseStore.(types.Flusher).Flush() + ds.iavlStore.(types.Flusher).Flush() } // ---------------------------------------- @@ -755,22 +784,25 @@ func (ds *defaultStore) ClearCache() { // for debugging func (ds *defaultStore) Print() { - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:baseStore...") - store.Print(ds.baseStore) - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:iavlStore...") - store.Print(ds.iavlStore) - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:cacheTypes...") + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:baseStore...")) + utils.Print(ds.baseStore) + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:iavlStore...")) + utils.Print(ds.iavlStore) + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:cacheTypes...")) for tid, typ := range ds.cacheTypes { - fmt.Printf("- %v: %v\n", tid, typ) + fmt.Printf("- %v: %v\n", tid, + stringz.TrimN(fmt.Sprintf("%v", typ), 50)) } - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:cacheNodes...") + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:cacheNodes...")) for loc, bn := range ds.cacheNodes { - fmt.Printf("- %v: %v\n", loc, bn) + fmt.Printf("- %v: %v\n", loc, + stringz.TrimN(fmt.Sprintf("%v", bn), 50)) } + fmt.Println(colors.Red("//----------------------------------------")) } // ---------------------------------------- diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go index c5b72336c83..28e97ff2b5b 100644 --- a/gnovm/pkg/gnolang/transcribe.go +++ b/gnovm/pkg/gnolang/transcribe.go @@ -271,7 +271,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } else { cnn = cnn2.(*FuncLitExpr) } - for idx := range cnn.Body { + // 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 @@ -383,7 +384,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } else { cnn = cnn2.(*BlockStmt) } - for idx := range cnn.Body { + // 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 @@ -393,7 +395,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } case *BranchStmt: case *DeclStmt: - for idx := range cnn.Body { + // 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 @@ -438,7 +441,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc return } } - for idx := range cnn.Body { + // 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 @@ -488,7 +492,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } else { cnn = cnn2.(*IfCaseStmt) } - for idx := range cnn.Body { + // 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 @@ -525,7 +530,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc return } } - for idx := range cnn.Body { + // 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 @@ -565,7 +571,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc if isStopOrSkip(nc, c) { return } - for idx := range cnn.Body { + // 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 @@ -640,7 +647,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc return } } - for idx := range cnn.Body { + // 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 @@ -666,7 +674,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } else { cnn = cnn2.(*FuncDecl) } - for idx := range cnn.Body { + // 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 diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go new file mode 100644 index 00000000000..3f25667f353 --- /dev/null +++ b/gnovm/pkg/gnolang/type_check.go @@ -0,0 +1,927 @@ +package gnolang + +import ( + "fmt" + "reflect" + + "github.com/gnolang/gno/tm2/pkg/errors" +) + +// here are a range of rules predefined for preprocessor to check the compatibility between operands and operators +// e,g. for binary expr x + y, x, y can only be numeric or string, 1+2, "a" + "b" +// this is used in assertCompatible()s. +var ( + binaryChecker = map[Word]func(t Type) bool{ + ADD: isNumericOrString, + SUB: isNumeric, + MUL: isNumeric, + QUO: isNumeric, + REM: isIntNum, + SHL: isIntNum, + SHR: isIntNum, + BAND: isIntNum, // bit ops + XOR: isIntNum, + BOR: isIntNum, + BAND_NOT: isIntNum, + LAND: isBoolean, // logic + LOR: isBoolean, + LSS: isOrdered, // compare + LEQ: isOrdered, + GTR: isOrdered, + GEQ: isOrdered, + } + // TODO: star, addressable + unaryChecker = map[Word]func(t Type) bool{ + ADD: isNumeric, + SUB: isNumeric, + XOR: isIntNum, + NOT: isBoolean, + } + IncDecStmtChecker = map[Word]func(t Type) bool{ + INC: isNumeric, + DEC: isNumeric, + } + AssignStmtChecker = map[Word]func(t Type) bool{ + ADD_ASSIGN: isNumericOrString, + SUB_ASSIGN: isNumeric, + MUL_ASSIGN: isNumeric, + QUO_ASSIGN: isNumeric, + REM_ASSIGN: isIntNum, + SHL_ASSIGN: isNumeric, + SHR_ASSIGN: isNumeric, + BAND_ASSIGN: isIntNum, + XOR_ASSIGN: isIntNum, + BOR_ASSIGN: isIntNum, + BAND_NOT_ASSIGN: isIntNum, + } +) + +type category int + +const ( + IsBoolean category = 1 << iota + IsInteger + IsFloat + IsString + IsBigInt + IsBigDec + + IsNumeric = IsInteger | IsFloat | IsBigInt | IsBigDec + IsOrdered = IsNumeric | IsString +) + +func (pt PrimitiveType) category() category { + switch pt.Kind() { + case BoolKind: + return IsBoolean + case StringKind: + return IsString + case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind, UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind: + return IsInteger // UntypedRuneType is int32kind, DataByteType is uint8 kind + case Float32Kind, Float64Kind: + return IsFloat + case BigintKind: + return IsBigInt + case BigdecKind: + return IsBigDec + default: + panic(fmt.Sprintf("unexpected primitive type %v", pt)) + } +} + +func isOrdered(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsOrdered != 0 + default: + return false + } +} + +func isBoolean(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsBoolean != 0 + default: + return false + } +} + +// rune can be numeric and string +func isNumeric(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsNumeric != 0 + default: + return false + } +} + +func isIntNum(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsInteger != 0 || t.category()&IsBigInt != 0 + default: + return false + } +} + +func isNumericOrString(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsNumeric != 0 || t.category()&IsString != 0 + default: + return false + } +} + +// =========================================================== +func assertComparable(xt, dt Type) { + switch baseOf(dt).(type) { + case *SliceType, *FuncType, *MapType: + if xt != nil { + panic(fmt.Sprintf("%v can only be compared to nil", dt)) + } + } + assertComparable2(dt) +} + +// assert value with dt is comparable +func assertComparable2(dt Type) { + if debug { + debug.Printf("assertComparable2 dt: %v \n", dt) + } + switch cdt := baseOf(dt).(type) { + case PrimitiveType: + case *ArrayType: + switch baseOf(cdt.Elem()).(type) { + case PrimitiveType, *PointerType, *InterfaceType, *NativeType, *ArrayType, *StructType, *ChanType: + assertComparable2(cdt.Elem()) + default: + panic(fmt.Sprintf("%v is not comparable", dt)) + } + case *StructType: + for _, f := range cdt.Fields { + switch cft := baseOf(f.Type).(type) { + case PrimitiveType, *PointerType, *InterfaceType, *NativeType, *ArrayType, *StructType: + assertComparable2(cft) + default: + panic(fmt.Sprintf("%v is not comparable", dt)) + } + } + case *PointerType: // &a == &b + case *InterfaceType: + case *SliceType, *FuncType, *MapType: + case *NativeType: + if !cdt.Type.Comparable() { + panic(fmt.Sprintf("%v is not comparable", dt)) + } + default: + panic(fmt.Sprintf("%v is not comparable", dt)) + } +} + +func maybeNil(t Type) bool { + switch cxt := baseOf(t).(type) { + case *SliceType, *FuncType, *MapType, *InterfaceType, *PointerType, *ChanType: // we don't have unsafePointer + return true + case *NativeType: + switch nk := cxt.Type.Kind(); nk { + case reflect.Slice, reflect.Func, reflect.Map, reflect.Interface, reflect.Pointer: + return true + default: + return false + } + default: + return false + } +} + +func checkSame(at, bt Type, msg string) error { + if debug { + debug.Printf("checkSame, at: %v bt: %v \n", at, bt) + } + if at.TypeID() != bt.TypeID() { + return errors.New("incompatible types %v and %v %s", + at.TypeID(), bt.TypeID(), msg) + } + return nil +} + +func assertAssignableTo(xt, dt Type, autoNative bool) { + err := checkAssignableTo(xt, dt, autoNative) + if err != nil { + panic(err.Error()) + } +} + +// Assert that xt can be assigned as dt (dest type). +// If autoNative is true, a broad range of xt can match against +// a target native dt type, if and only if dt is a native type. +func checkAssignableTo(xt, dt Type, autoNative bool) error { + if debug { + debug.Printf("checkAssignableTo, xt: %v dt: %v \n", xt, dt) + } + // case0 + if xt == nil { // see test/files/types/eql_0f18 + if !maybeNil(dt) { + panic(fmt.Sprintf("invalid operation, nil can not be compared to %v", dt)) + } + return nil + } else if dt == nil { // _ = xxx, assign8.gno, 0f31. else cases? + return nil + } + // case3 + if dt.Kind() == InterfaceKind { // note native interface + if idt, ok := baseOf(dt).(*InterfaceType); ok { + if idt.IsEmptyInterface() { // XXX, can this be merged with IsImplementedBy? + // if dt is an empty Gno interface, any x ok. + return nil // ok + } else if err := idt.VerifyImplementedBy(xt); err == nil { + // if dt implements idt, ok. + return nil // ok + } else { + return errors.New( + "%s does not implement %s (%s)", + xt.String(), + dt.String(), + err.Error()) + } + } else if ndt, ok := baseOf(dt).(*NativeType); ok { + nidt := ndt.Type + if nidt.NumMethod() == 0 { + // if dt is an empty Go native interface, ditto. + return nil // ok + } else if nxt, ok := baseOf(xt).(*NativeType); ok { + // if xt has native base, do the naive native. + if nxt.Type.AssignableTo(nidt) { + return nil // ok + } else { + return errors.New( + "cannot use %s as %s", + nxt.String(), + nidt.String()) + } + } else if pxt, ok := baseOf(xt).(*PointerType); ok { + nxt, ok := pxt.Elt.(*NativeType) + if !ok { + return errors.New( + "pointer to non-native type cannot satisfy non-empty native interface; %s doesn't implement %s", + pxt.String(), + nidt.String()) + } + // if xt has native base, do the naive native. + if reflect.PtrTo(nxt.Type).AssignableTo(nidt) { + return nil // ok + } else { + return errors.New( + "cannot use %s as %s", + pxt.String(), + nidt.String()) + } + } else if xdt, ok := xt.(*DeclaredType); ok { + if gno2GoTypeMatches(baseOf(xdt), ndt.Type) { + return nil + } // not check against native interface + } else { + return errors.New( + "unexpected type pair: cannot use %s as %s", + xt.String(), + dt.String()) + } + } else { + return errors.New("should not happen") + } + } + + // case2 + // Special case if xt or dt is *PointerType to *NativeType, + // convert to *NativeType of pointer kind. + if pxt, ok := xt.(*PointerType); ok { + // *gonative{x} is gonative{*x} + //nolint:misspell + if enxt, ok := pxt.Elt.(*NativeType); ok { + xt = &NativeType{ + Type: reflect.PtrTo(enxt.Type), + } + } + } + if pdt, ok := dt.(*PointerType); ok { + // *gonative{x} is gonative{*x} + if endt, ok := pdt.Elt.(*NativeType); ok { + dt = &NativeType{ + Type: reflect.PtrTo(endt.Type), + } + } + } + + // Special case of xt or dt is *DeclaredType, + // allow implicit conversion unless both are declared. + // TODO simplify with .IsNamedType(). + if dxt, ok := xt.(*DeclaredType); ok { + if ddt, ok := dt.(*DeclaredType); ok { + // types must match exactly. + if !dxt.sealed && !ddt.sealed && + dxt.PkgPath == ddt.PkgPath && + dxt.Name == ddt.Name { // not yet sealed + return nil // ok + } else if dxt.TypeID() == ddt.TypeID() { + return nil // ok + } else { + return errors.New( + "cannot use %s as %s without explicit conversion", + dxt.String(), + ddt.String()) + } + } else { + // special case if implicitly named primitive type. + // TODO simplify with .IsNamedType(). + if _, ok := dt.(PrimitiveType); ok { + return errors.New( + "cannot use %s as %s without explicit conversion", + dxt.String(), + dt.String()) + } else { + // carry on with baseOf(dxt) + xt = dxt.Base // set as base to do the rest check + } + } + } else if ddt, ok := dt.(*DeclaredType); ok { + // special case if implicitly named primitive type. + // TODO simplify with .IsNamedType(). + if _, ok := xt.(PrimitiveType); ok { // e.g. 1 == Int(1) + if debug { + debug.Printf("xt is primitiveType: %v, ddt: %v \n", xt, ddt) + } + // this is special when dt is the declared type of x + if !isUntyped(xt) { + return errors.New( + "cannot use %s as %s without explicit conversion", + xt.String(), + ddt.String()) + } else { // xt untyped, carry on with check below + dt = ddt.Base + } + } else { + dt = ddt.Base + } + } + + // General cases. + switch cdt := dt.(type) { + case PrimitiveType: // case 1 + // if xt is untyped, ensure dt is compatible. + switch xt { + case UntypedBoolType: + if dt.Kind() == BoolKind { + return nil // ok + } else { + return errors.New( + "cannot use untyped bool as %s", + dt.Kind()) + } + case UntypedStringType: + if dt.Kind() == StringKind { + return nil // ok + } else { + return errors.New( + "cannot use untyped string as %s", + dt.Kind()) + } + // XXX, this is a loose check, we don't have the context + // to check if it is an exact integer, e.g. 1.2 or 1.0(1.0 can be converted to int). + // this ensure expr like (a % 1.0) pass check, while + // expr like (a % 1.2) panic at ConvertUntypedTo, which is a delayed assertion after const evaluated. + // assignable does not guarantee convertible. + case UntypedBigdecType: + switch dt.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, + Int64Kind, UintKind, Uint8Kind, Uint16Kind, + Uint32Kind, Uint64Kind, BigdecKind, Float32Kind, Float64Kind: + return nil // ok + default: + panic(fmt.Sprintf( + "cannot use untyped Bigdec as %s", + dt.Kind())) + } + case UntypedBigintType: + switch dt.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, + Int64Kind, UintKind, Uint8Kind, Uint16Kind, + Uint32Kind, Uint64Kind, BigintKind, BigdecKind, Float32Kind, Float64Kind: // see 0d0 + return nil // ok + default: + return errors.New( + "cannot use untyped Bigint as %s", + dt.Kind()) + } + case UntypedRuneType: + switch dt.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, + Int64Kind, UintKind, Uint8Kind, Uint16Kind, + Uint32Kind, Uint64Kind, BigintKind, BigdecKind, Float32Kind, Float64Kind: + return nil // ok + default: + return errors.New( + "cannot use untyped rune as %s", + dt.Kind()) + } + + default: + if isUntyped(xt) { + panic("unexpected untyped type") + } + if xt.TypeID() == cdt.TypeID() { + return nil // ok + } + } + case *PointerType: // case 4 from here on + if pt, ok := xt.(*PointerType); ok { + return checkAssignableTo(pt.Elt, cdt.Elt, false) + } + case *ArrayType: + if at, ok := xt.(*ArrayType); ok { + if at.Len != cdt.Len { + return errors.New( + "cannot use %s as %s", + at.String(), + cdt.String()) + } + err := checkSame(at.Elt, cdt.Elt, "") + if err != nil { + return errors.New( + "cannot use %s as %s", + at.String(), + cdt.String()) + } + return nil + } + case *SliceType: + if st, ok := xt.(*SliceType); ok { + if cdt.Vrd { + return checkAssignableTo(st.Elt, cdt.Elt, false) + } else { + err := checkSame(st.Elt, cdt.Elt, "") + if err != nil { + return errors.New( + "cannot use %s as %s", + st.String(), + cdt.String()) + } + return nil + } + } + case *MapType: + if mt, ok := xt.(*MapType); ok { + err := checkSame(mt.Key, cdt.Key, "") + if err != nil { + return errors.New( + "cannot use %s as %s", + mt.String(), + cdt.String()).Stacktrace() + } + return nil + } + case *InterfaceType: + return errors.New("should not happen") + case *DeclaredType: + panic("should not happen") + case *FuncType, *StructType, *PackageType, *ChanType, *TypeType: + if xt.TypeID() == cdt.TypeID() { + return nil // ok + } + case *NativeType: + if !autoNative { + if debug { + debug.Printf("native type, xt.TypeID: %v, cdt.TypeID: %v \n", xt.TypeID(), cdt.TypeID()) + } + if xt.TypeID() == cdt.TypeID() { + return nil // ok + } + } else { + // autoNative, so check whether matches. + // xt: any type but a *DeclaredType; could be native. + // cdt: actual concrete native target type. + // ie, if cdt can match against xt. + if gno2GoTypeMatches(xt, cdt.Type) { + return nil // ok + } + } + default: + return errors.New( + "unexpected type %s", + dt.String()) + } + return errors.New( + "cannot use %s as %s", + xt.String(), + dt.String()).Stacktrace() +} + +// =========================================================== +func (x *BinaryExpr) checkShiftLhs(dt Type) { + if checker, ok := binaryChecker[x.Op]; ok { + if !checker(dt) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(dt))) + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } +} + +// AssertCompatible works as a pre-check prior to checkOrConvertType. +// It checks against expressions to ensure the compatibility between operands and operators. +// e.g. "a" << 1, the left hand operand is not compatible with <<, it will fail the check. +// Overall,it efficiently filters out incompatible expressions, stopping before the next +// checkOrConvertType() operation to optimize performance. +func (x *BinaryExpr) AssertCompatible(lt, rt Type) { + // native type will be converted to gno in latter logic, + // this check logic will be conduct again from trans_leave *BinaryExpr. + lnt, lin := lt.(*NativeType) + rnt, rin := rt.(*NativeType) + if lin && rin { + if lt.TypeID() != rt.TypeID() { + panic(fmt.Sprintf( + "incompatible operands in binary expression: %s %s %s", + lt.TypeID(), x.Op, rt.TypeID())) + } + } + if lin { + if _, ok := go2GnoBaseType(lnt.Type).(PrimitiveType); ok { + return + } + } + if rin { + if _, ok := go2GnoBaseType(rnt.Type).(PrimitiveType); ok { + return + } + } + + xt, dt := lt, rt + if shouldSwapOnSpecificity(lt, rt) { + xt, dt = dt, xt + } + + if isComparison(x.Op) { + switch x.Op { + case EQL, NEQ: + assertComparable(xt, dt) + if !isUntyped(xt) && !isUntyped(dt) { + assertAssignableTo(xt, dt, false) + } + case LSS, LEQ, GTR, GEQ: + if checker, ok := binaryChecker[x.Op]; ok { + x.checkCompatibility(xt, dt, checker, x.Op.TokenString()) + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } + default: + panic("invalid comparison operator") + } + } else { + if checker, ok := binaryChecker[x.Op]; ok { + x.checkCompatibility(xt, dt, checker, x.Op.TokenString()) + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } + + switch x.Op { + case QUO, REM: + // special case of zero divisor + if isQuoOrRem(x.Op) { + if rcx, ok := x.Right.(*ConstExpr); ok { + if rcx.TypedValue.isZero() { + panic("invalid operation: division by zero") + } + } + } + default: + // do nothing + } + } +} + +// Check compatibility of the destination type (dt) with the operator. +// If both source type (xt) and destination type (dt) are typed: +// Verify that xt is assignable to dt. +// If xt is untyped: +// The function checkOrConvertType will be invoked after this check. +// NOTE: dt is established based on a specificity check between xt and dt, +// confirming dt as the appropriate destination type for this context. +func (x *BinaryExpr) checkCompatibility(xt, dt Type, checker func(t Type) bool, OpStr string) { + if !checker(dt) { + panic(fmt.Sprintf("operator %s not defined on: %v", OpStr, kindString(dt))) + } + + // if both typed + if !isUntyped(xt) && !isUntyped(dt) { + err := checkAssignableTo(xt, dt, false) + if err != nil { + panic(fmt.Sprintf("invalid operation: mismatched types %v and %v", xt, dt)) + } + } +} + +func (x *UnaryExpr) AssertCompatible(t Type) { + if nt, ok := t.(*NativeType); ok { + if _, ok := go2GnoBaseType(nt.Type).(PrimitiveType); ok { + return + } + } + // check compatible + if checker, ok := unaryChecker[x.Op]; ok { + if !checker(t) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(t))) + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } +} + +func (x *IncDecStmt) AssertCompatible(t Type) { + if nt, ok := t.(*NativeType); ok { + if _, ok := go2GnoBaseType(nt.Type).(PrimitiveType); ok { + return + } + } + // check compatible + if checker, ok := IncDecStmtChecker[x.Op]; ok { + if !checker(t) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(t))) + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } +} + +func assertIndexTypeIsInt(kt Type) { + if kt.Kind() != IntKind { + panic(fmt.Sprintf("index type should be int, but got %v", kt)) + } +} + +func (x *RangeStmt) AssertCompatible(store Store, last BlockNode) { + if x.Op != ASSIGN { + return + } + if isBlankIdentifier(x.Key) && isBlankIdentifier(x.Value) { + // both "_" + return + } + assertValidAssignLhs(store, last, x.Key) + // if is valid left value + + kt := evalStaticTypeOf(store, last, x.Key) + var vt Type + if x.Value != nil { + vt = evalStaticTypeOf(store, last, x.Value) + } + + xt := evalStaticTypeOf(store, last, x.X) + switch cxt := xt.(type) { + case *MapType: + assertAssignableTo(cxt.Key, kt, false) + if vt != nil { + assertAssignableTo(cxt.Value, vt, false) + } + case *SliceType: + assertIndexTypeIsInt(kt) + if vt != nil { + assertAssignableTo(cxt.Elt, vt, false) + } + case *ArrayType: + assertIndexTypeIsInt(kt) + if vt != nil { + assertAssignableTo(cxt.Elt, vt, false) + } + case PrimitiveType: + if cxt.Kind() == StringKind { + if kt != nil && kt.Kind() != IntKind { + panic(fmt.Sprintf("index type should be int, but got %v", kt)) + } + if vt != nil { + if vt.Kind() != Int32Kind { // rune + panic(fmt.Sprintf("value type should be int32, but got %v", kt)) + } + } + } + } +} + +func (x *AssignStmt) AssertCompatible(store Store, last BlockNode) { + if x.Op == ASSIGN || x.Op == DEFINE { + if len(x.Lhs) > len(x.Rhs) { + if len(x.Rhs) != 1 { + panic(fmt.Sprintf("assignment mismatch: %d variables but %d values", len(x.Lhs), len(x.Rhs))) + } + switch cx := x.Rhs[0].(type) { + case *CallExpr: + // Call case: a, b = x(...) + ift := evalStaticTypeOf(store, last, cx.Func) + cft := getGnoFuncTypeOf(store, ift) + if len(x.Lhs) != len(cft.Results) { + panic(fmt.Sprintf( + "assignment mismatch: "+ + "%d variables but %s returns %d values", + len(x.Lhs), cx.Func.String(), len(cft.Results))) + } + if x.Op == ASSIGN { + // check assignable + for i, lx := range x.Lhs { + if !isBlankIdentifier(lx) { + assertValidAssignLhs(store, last, lx) + lxt := evalStaticTypeOf(store, last, lx) + assertAssignableTo(cft.Results[i].Type, lxt, false) + } + } + } + case *TypeAssertExpr: + // Type-assert case: a, ok := x.(type) + if len(x.Lhs) != 2 { + panic("should not happen") + } + if x.Op == ASSIGN { + // check assignable to first value + if !isBlankIdentifier(x.Lhs[0]) { // see composite3.gno + assertValidAssignLhs(store, last, x.Lhs[0]) + dt := evalStaticTypeOf(store, last, x.Lhs[0]) + ift := evalStaticTypeOf(store, last, cx) + assertAssignableTo(ift, dt, false) + } + if !isBlankIdentifier(x.Lhs[1]) { // see composite3.gno + assertValidAssignLhs(store, last, x.Lhs[1]) + dt := evalStaticTypeOf(store, last, x.Lhs[1]) + if dt.Kind() != BoolKind { // typed, not bool + panic(fmt.Sprintf("want bool type got %v", dt)) + } + } + } + cx.HasOK = true + case *IndexExpr: // must be with map type when len(Lhs) > len(Rhs) + if len(x.Lhs) != 2 { + panic("should not happen") + } + if x.Op == ASSIGN { + if !isBlankIdentifier(x.Lhs[0]) { + assertValidAssignLhs(store, last, x.Lhs[0]) + lt := evalStaticTypeOf(store, last, x.Lhs[0]) + if _, ok := cx.X.(*NameExpr); ok { + rt := evalStaticTypeOf(store, last, cx.X) + if mt, ok := rt.(*MapType); ok { + assertAssignableTo(mt.Value, lt, false) + } + } else if _, ok := cx.X.(*CompositeLitExpr); ok { + cpt := evalStaticTypeOf(store, last, cx.X) + if mt, ok := cpt.(*MapType); ok { + assertAssignableTo(mt.Value, lt, false) + } else { + panic("should not happen") + } + } + } + if !isBlankIdentifier(x.Lhs[1]) { + assertValidAssignLhs(store, last, x.Lhs[1]) + dt := evalStaticTypeOf(store, last, x.Lhs[1]) + if dt != nil && dt.Kind() != BoolKind { // typed, not bool + panic(fmt.Sprintf("want bool type got %v", dt)) + } + } + } + cx.HasOK = true + default: + panic(fmt.Sprintf("RHS should not be %v when len(Lhs) > len(Rhs)", cx)) + } + } else { // len(Lhs) == len(Rhs) + if x.Op == ASSIGN { + // assert valid left value + for _, lx := range x.Lhs { + assertValidAssignLhs(store, last, lx) + } + } + } + } else { // Ops other than assign and define + // If this is an assignment operation, ensure there's only 1 + // expr on lhs/rhs. + if len(x.Lhs) != 1 || len(x.Rhs) != 1 { + panic("assignment operator " + x.Op.TokenString() + + " requires only one expression on lhs and rhs") + } + for i, lx := range x.Lhs { + lt := evalStaticTypeOf(store, last, lx) + rt := evalStaticTypeOf(store, last, x.Rhs[i]) + + if checker, ok := AssignStmtChecker[x.Op]; ok { + if !checker(lt) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(lt))) + } + switch x.Op { + case ADD_ASSIGN, SUB_ASSIGN, MUL_ASSIGN, QUO_ASSIGN, REM_ASSIGN, BAND_ASSIGN, BOR_ASSIGN, BAND_NOT_ASSIGN, XOR_ASSIGN: + // check when both typed + if !isUntyped(lt) && !isUntyped(rt) { // in this stage, lt or rt maybe untyped, not converted yet + if lt != nil && rt != nil { + if lt.TypeID() != rt.TypeID() { + panic(fmt.Sprintf("invalid operation: mismatched types %v and %v", lt, rt)) + } + } + } + default: + // do nothing + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } + } + } +} + +// misc +func assertValidAssignLhs(store Store, last BlockNode, lx Expr) { + shouldPanic := true + switch clx := lx.(type) { + case *NameExpr, *StarExpr, *SelectorExpr: + shouldPanic = false + case *IndexExpr: + xt := evalStaticTypeOf(store, last, clx.X) + shouldPanic = xt != nil && xt.Kind() == StringKind + default: + } + if shouldPanic { + panic(fmt.Sprintf("cannot assign to %v", lx)) + } +} + +func kindString(xt Type) string { + if xt != nil { + return xt.Kind().String() + } + return "nil" +} + +func isQuoOrRem(op Word) bool { + switch op { + case QUO, QUO_ASSIGN, REM, REM_ASSIGN: + return true + default: + return false + } +} + +func isComparison(op Word) bool { + switch op { + case EQL, NEQ, LSS, LEQ, GTR, GEQ: + return true + default: + return false + } +} + +// shouldSwapOnSpecificity determines the potential direction for +// checkOrConvertType. it checks whether a swap is needed between two types +// based on their specificity. If t2 has a lower specificity than t1, it returns +// false, indicating no swap is needed. If t1 has a lower specificity than t2, +// it returns true, indicating a swap is needed. +func shouldSwapOnSpecificity(t1, t2 Type) bool { + // check nil + if t1 == nil { // see test file 0f46 + return false // also with both nil + } else if t2 == nil { + return true + } + + // check interface + if it1, ok := baseOf(t1).(*InterfaceType); ok { + if it1.IsEmptyInterface() { + return true // left empty interface + } else { + if _, ok := baseOf(t2).(*InterfaceType); ok { + return false + } else { + return true // right not interface + } + } + } else if _, ok := t2.(*InterfaceType); ok { + return false // left not interface, right is interface + } + + // primitive types + t1s, t2s := 0, 0 + if t1p, ok := t1.(PrimitiveType); ok { + t1s = t1p.Specificity() + } + if t2p, ok := t2.(PrimitiveType); ok { + t2s = t2p.Specificity() + } + if t1s < t2s { + // NOTE: higher specificity has lower value, so backwards. + return true + } else { + return false + } +} + +func isBlankIdentifier(x Expr) bool { + if nx, ok := x.(*NameExpr); ok { + return nx.Name == "_" + } + return false +} diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index e1814e8f243..b43f623ea99 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -24,6 +24,7 @@ type Type interface { String() string // for dev/debugging Elem() Type // for TODO... types GetPkgPath() string + IsNamed() bool // named vs unname type. property as a method } type TypeID string @@ -323,6 +324,10 @@ func (pt PrimitiveType) GetPkgPath() string { return "" } +func (pt PrimitiveType) IsNamed() bool { + return true +} + // ---------------------------------------- // Field type (partial) @@ -369,6 +374,10 @@ func (ft FieldType) GetPkgPath() string { panic("FieldType is a pseudotype with no package path") } +func (ft FieldType) IsNamed() bool { + panic("FieldType is a pseudotype with no property called named") +} + // ---------------------------------------- // FieldTypeList @@ -528,6 +537,10 @@ func (at *ArrayType) GetPkgPath() string { return "" } +func (at *ArrayType) IsNamed() bool { + return false +} + // ---------------------------------------- // Slice type @@ -574,6 +587,10 @@ func (st *SliceType) GetPkgPath() string { return "" } +func (st *SliceType) IsNamed() bool { + return false +} + // ---------------------------------------- // Pointer type @@ -612,6 +629,10 @@ func (pt *PointerType) GetPkgPath() string { return pt.Elt.GetPkgPath() } +func (pt *PointerType) IsNamed() bool { + return false +} + func (pt *PointerType) FindEmbeddedFieldType(callerPath string, n Name, m map[Type]struct{}) ( trail []ValuePath, hasPtr bool, rcvr Type, field Type, accessError bool, ) { @@ -747,6 +768,10 @@ func (st *StructType) GetPkgPath() string { return st.PkgPath } +func (st *StructType) IsNamed() bool { + return false +} + // NOTE only works for exposed non-embedded fields. func (st *StructType) GetPathForName(n Name) ValuePath { for i := 0; i < len(st.Fields); i++ { @@ -867,6 +892,10 @@ func (pt *PackageType) GetPkgPath() string { panic("package types has no package path (unlike package values)") } +func (pt *PackageType) IsNamed() bool { + panic("package types have no property called named") +} + // ---------------------------------------- // Interface type @@ -926,6 +955,10 @@ func (it *InterfaceType) GetPkgPath() string { return it.PkgPath } +func (it *InterfaceType) IsNamed() bool { + return false +} + func (it *InterfaceType) FindEmbeddedFieldType(callerPath string, n Name, m map[Type]struct{}) ( trail []ValuePath, hasPtr bool, rcvr Type, ft Type, accessError bool, ) { @@ -976,13 +1009,13 @@ func (it *InterfaceType) FindEmbeddedFieldType(callerPath string, n Name, m map[ // For run-time type assertion. // TODO: optimize somehow. -func (it *InterfaceType) IsImplementedBy(ot Type) (result bool) { +func (it *InterfaceType) VerifyImplementedBy(ot Type) error { for _, im := range it.Methods { if im.Type.Kind() == InterfaceKind { // field is embedded interface... im2 := baseOf(im.Type).(*InterfaceType) - if !im2.IsImplementedBy(ot) { - return false + if err := im2.VerifyImplementedBy(ot); err != nil { + return err } else { continue } @@ -990,7 +1023,7 @@ func (it *InterfaceType) IsImplementedBy(ot Type) (result bool) { // find method in field. tr, hp, rt, ft, _ := findEmbeddedFieldType(it.PkgPath, ot, im.Name, nil) if tr == nil { // not found. - return false + return fmt.Errorf("missing method %s", im.Name) } if nft, ok := ft.(*NativeType); ok { // Treat native function types as autoNative calls. @@ -1000,22 +1033,26 @@ func (it *InterfaceType) IsImplementedBy(ot Type) (result bool) { // ie, if each of ft's arg types can match // against the desired arg types in im.Types. if !gno2GoTypeMatches(im.Type, nft.Type) { - return false + return fmt.Errorf("wrong type for method %s", im.Name) } } else if mt, ok := ft.(*FuncType); ok { // if method is pointer receiver, check addressability: if _, ptrRcvr := rt.(*PointerType); ptrRcvr && !hp { - return false // not addressable. + return fmt.Errorf("method %s has pointer receiver", im.Name) // not addressable. } // check for func type equality. dmtid := mt.TypeID() imtid := im.Type.TypeID() if dmtid != imtid { - return false + return fmt.Errorf("wrong type for method %s", im.Name) } } } - return true + return nil +} + +func (it *InterfaceType) IsImplementedBy(ot Type) bool { + return it.VerifyImplementedBy(ot) == nil } func (it *InterfaceType) GetPathForName(n Name) ValuePath { @@ -1073,6 +1110,10 @@ func (ct *ChanType) GetPkgPath() string { return "" } +func (ct *ChanType) IsNamed() bool { + return false +} + // ---------------------------------------- // Function type @@ -1280,6 +1321,10 @@ func (ft *FuncType) GetPkgPath() string { panic("function types have no package path") } +func (ft *FuncType) IsNamed() bool { + return false +} + func (ft *FuncType) HasVarg() bool { if numParams := len(ft.Params); numParams == 0 { return false @@ -1338,6 +1383,10 @@ func (mt *MapType) GetPkgPath() string { return "" } +func (mt *MapType) IsNamed() bool { + return false +} + // ---------------------------------------- // Type (typeval) type @@ -1366,6 +1415,10 @@ func (tt *TypeType) GetPkgPath() string { panic("typeval types have no package path") } +func (tt *TypeType) IsNamed() bool { + panic("typeval types have no property called 'named'") +} + // ---------------------------------------- // Declared type // Declared types have a name, base (underlying) type, @@ -1450,6 +1503,10 @@ func (dt *DeclaredType) GetPkgPath() string { return dt.PkgPath } +func (dt *DeclaredType) IsNamed() bool { + return true +} + func (dt *DeclaredType) DefineMethod(fv *FuncValue) { if !dt.TryDefineMethod(fv) { panic(fmt.Sprintf("redeclaration of method %s.%s", @@ -1767,6 +1824,14 @@ func (nt *NativeType) GetPkgPath() string { return "go:" + nt.Type.PkgPath() } +func (nt *NativeType) IsNamed() bool { + if nt.Type.Name() != "" { + return true + } else { + return false + } +} + func (nt *NativeType) GnoType(store Store) Type { if nt.gnoType == nil { nt.gnoType = store.Go2GnoType(nt.Type) @@ -1895,6 +1960,10 @@ func (bt blockType) GetPkgPath() string { panic("blockType has no package path") } +func (bt blockType) IsNamed() bool { + panic("blockType has no property called named") +} + // ---------------------------------------- // tupleType @@ -1945,6 +2014,10 @@ func (tt *tupleType) GetPkgPath() string { panic("typleType has no package path") } +func (tt *tupleType) IsNamed() bool { + panic("typleType has no property called named") +} + // ---------------------------------------- // RefType @@ -1965,11 +2038,15 @@ func (rt RefType) String() string { } func (rt RefType) Elem() Type { - panic("should not happen") + panic("RefType has no elem type") } func (rt RefType) GetPkgPath() string { - panic("should not happen") + panic("RefType has no package path") +} + +func (rt RefType) IsNamed() bool { + panic("RefType has no property called named") } // ---------------------------------------- @@ -2002,6 +2079,10 @@ func (mn MaybeNativeType) GetPkgPath() string { return mn.Type.GetPkgPath() } +func (mn MaybeNativeType) IsNamed() bool { + return mn.Type.IsNamed() +} + // ---------------------------------------- // Kind @@ -2126,11 +2207,11 @@ func KindOf(t Type) Kind { // ---------------------------------------- // main type-assertion functions. -// TODO: document what class of problems its for. +// Only for runtime debugging. // One of them can be nil, and this lets uninitialized primitives // and others serve as empty values. See doOpAdd() -// usage: if debug { assertSameTypes() } -func assertSameTypes(lt, rt Type) { +// usage: if debug { debugAssertSameTypes() } +func debugAssertSameTypes(lt, rt Type) { if lt == nil && rt == nil { // both are nil. } else if lt == nil || rt == nil { @@ -2155,8 +2236,10 @@ func assertSameTypes(lt, rt Type) { } } -// Like assertSameTypes(), but more relaxed, for == and !=. -func assertEqualityTypes(lt, rt Type) { +// Only for runtime debugging. +// Like debugAssertSameTypes(), but more relaxed, for == and !=. +// usage: if debug { debugAssertEqualityTypes() } +func debugAssertEqualityTypes(lt, rt Type) { if lt == nil && rt == nil { // both are nil. } else if lt == nil || rt == nil { @@ -2285,6 +2368,7 @@ func fillEmbeddedName(ft *FieldType) { ft.Embedded = true } +// TODO: empty interface? refer to assertAssignableTo func IsImplementedBy(it Type, ot Type) bool { switch cbt := baseOf(it).(type) { case *InterfaceType: @@ -2419,7 +2503,7 @@ func specifyType(store Store, lookup map[Name]Type, tmpl Type, spec Type, specTy generic := ct.Generic[:len(ct.Generic)-len(".Elem()")] match, ok := lookup[generic] if ok { - checkType(spec, match.Elem(), false) + assertAssignableTo(spec, match.Elem(), false) return // ok } else { // Panic here, because we don't know whether T @@ -2433,7 +2517,7 @@ func specifyType(store Store, lookup map[Name]Type, tmpl Type, spec Type, specTy } else { match, ok := lookup[ct.Generic] if ok { - checkType(spec, match, false) + assertAssignableTo(spec, match, false) return // ok } else { if isUntyped(spec) { diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index df16ecf0ad9..880a75396ca 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -911,16 +911,18 @@ func UverseNode() *PackageNode { tt := arg0.TV.GetType() vv := defaultValue(m.Alloc, tt) m.Alloc.AllocatePointer() + hi := m.Alloc.NewHeapItem(TypedValue{ + T: tt, + V: vv, + }) m.PushValue(TypedValue{ T: m.Alloc.NewType(&PointerType{ Elt: tt, }), V: PointerValue{ - TV: &TypedValue{ - T: tt, - V: vv, - }, - Base: nil, + TV: &hi.Value, + Base: hi, + Index: 0, }, }) return diff --git a/gnovm/pkg/gnolang/uverse_test.go b/gnovm/pkg/gnolang/uverse_test.go index 7280d131ec5..7a6c0567e45 100644 --- a/gnovm/pkg/gnolang/uverse_test.go +++ b/gnovm/pkg/gnolang/uverse_test.go @@ -39,7 +39,7 @@ func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) { var a []string println(a) }`, - expected: "nil []string\n", + expected: "(nil []string)\n", }, { name: "print non-empty slice", @@ -57,7 +57,7 @@ func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) { var a map[string]string println(a) }`, - expected: "nil map[string]string\n", + expected: "(nil map[string]string)\n", }, { name: "print non-empty map", diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 948730c4697..5da7c15bb05 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -41,6 +41,7 @@ func (*PackageValue) assertValue() {} func (*NativeValue) assertValue() {} func (*Block) assertValue() {} func (RefValue) assertValue() {} +func (*HeapItemValue) assertValue() {} const ( nilStr = "nil" @@ -64,6 +65,7 @@ var ( _ Value = &NativeValue{} _ Value = &Block{} _ Value = RefValue{} + _ Value = &HeapItemValue{} ) // ---------------------------------------- @@ -166,14 +168,20 @@ func (dbv DataByteValue) SetByte(b byte) { // Index is -1 for the shared "_" block var, // and -2 for (gno and native) map items. // +// A pointer constructed via a &x{} composite lit expression or constructed via +// new() or make() will have a virtual HeapItemValue as base. +// // Allocation for PointerValue is not immediate, // as usually PointerValues are temporary for assignment // or binary operations. When a pointer is to be // allocated, *Allocator.AllocatePointer() is called separately, // as in OpRef. +// +// Since PointerValue is used internally for assignment etc, +// it MUST stay minimal for computational efficiency. type PointerValue struct { TV *TypedValue // escape val if pointer to var. - Base Value // array/struct/block. + Base Value // array/struct/block, or heapitem. Index int // list/fields/values index, or -1 or -2 (see below). Key *TypedValue `json:",omitempty"` // for maps. } @@ -184,7 +192,6 @@ const ( PointerIndexNative = -3 // Base is *NativeValue. ) -/* func (pv *PointerValue) GetBase(store Store) Object { switch cbase := pv.Base.(type) { case nil: @@ -196,10 +203,9 @@ func (pv *PointerValue) GetBase(store Store) Object { case Object: return cbase default: - panic("should not happen") + panic(fmt.Sprintf("unexpected pointer base type %T", cbase)) } } -*/ // cu: convert untyped; pass false for const definitions // TODO: document as something that enables into-native assignment. @@ -595,6 +601,15 @@ func (fv *FuncValue) GetBodyFromSource(store Store) []Stmt { return fv.body } +func (fv *FuncValue) UpdateBodyFromSource() { + if fv.Source == nil { + panic(fmt.Sprintf( + "Source is missing for FuncValue %q", + fv.Name)) + } + fv.body = fv.Source.GetBody() +} + func (fv *FuncValue) GetSource(store Store) BlockNode { if rn, ok := fv.Source.(RefNode); ok { source := store.GetBlockNode(rn.GetLocation()) @@ -817,6 +832,7 @@ type PackageValue struct { fBlocksMap map[Name]*Block } +// IsRealm returns true if pv represents a realm. func (pv *PackageValue) IsRealm() bool { return IsRealmPath(pv.PkgPath) } @@ -1493,6 +1509,88 @@ func (tv *TypedValue) GetBigDec() *apd.Decimal { return tv.V.(BigdecValue).V } +// returns true if tv is zero +func (tv *TypedValue) isZero() bool { + if tv.T == nil { + panic("type should not be nil") + } + switch tv.T.Kind() { + case IntKind: + v := tv.GetInt() + if v == 0 { + return true + } + case Int8Kind: + v := tv.GetInt8() + if v == 0 { + return true + } + case Int16Kind: + v := tv.GetInt16() + if v == 0 { + return true + } + case Int32Kind: + v := tv.GetInt32() + if v == 0 { + return true + } + case Int64Kind: + v := tv.GetInt64() + if v == 0 { + return true + } + case UintKind: + v := tv.GetUint() + if v == 0 { + return true + } + case Uint8Kind: + v := tv.GetUint8() + if v == 0 { + return true + } + case Uint16Kind: + v := tv.GetUint16() + if v == 0 { + return true + } + case Uint32Kind: + v := tv.GetUint32() + if v == 0 { + return true + } + case Uint64Kind: + v := tv.GetUint64() + if v == 0 { + return true + } + case Float32Kind: + v := tv.GetFloat32() + if v == 0 { + return true + } + case Float64Kind: + v := tv.GetFloat64() + if v == 0 { + return true + } + case BigintKind: + v := tv.GetBigInt() + if v.Sign() == 0 { + return true + } + case BigdecKind: + v := tv.GetBigDec() + if v.Sign() == 0 { + return true + } + default: + panic("not numeric") + } + return false +} + func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey { // Special case when nil: has no separator. if tv.T == nil { @@ -2001,9 +2099,10 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed } default: panic(fmt.Sprintf( - "unexpected index base type %s (%v)", + "unexpected index base type %s (%v base %v)", tv.T.String(), - reflect.TypeOf(tv.T))) + reflect.TypeOf(tv.T), + reflect.TypeOf(baseOf(tv.T)))) } } @@ -2253,12 +2352,12 @@ func (tv *TypedValue) GetSlice2(alloc *Allocator, low, high, max int) TypedValue // TODO rename to BlockValue. type Block struct { - ObjectInfo // for closures - Source BlockNode - Values []TypedValue - Parent Value - Blank TypedValue // captures "_" // XXX remove and replace with global instance. - bodyStmt bodyStmt // XXX expose for persistence, not needed for MVP. + ObjectInfo + Source BlockNode + Values []TypedValue + Parent Value + Blank TypedValue // captures "_" // XXX remove and replace with global instance. + bodyStmt bodyStmt // XXX expose for persistence, not needed for MVP. } // NOTE: for allocation, use *Allocator.NewBlock. @@ -2343,7 +2442,7 @@ func (b *Block) GetPointerToInt(store Store, index int) PointerValue { func (b *Block) GetPointerTo(store Store, path ValuePath) PointerValue { if path.IsBlockBlankPath() { if debug { - if path.Name != "_" { + if path.Name != blankIdentifier { panic(fmt.Sprintf( "zero value path is reserved for \"_\", but got %s", path.Name)) @@ -2418,6 +2517,15 @@ type RefValue struct { Hash ValueHash `json:",omitempty"` } +// Base for a detached singleton (e.g. new(int) or &struct{}) +// Conceptually like a Block that holds one value. +// NOTE: could be renamed to HeapItemBaseValue. +// See also note in realm.go about auto-unwrapping. +type HeapItemValue struct { + ObjectInfo + Value TypedValue +} + // ---------------------------------------- func defaultStructFields(alloc *Allocator, st *StructType) []TypedValue { @@ -2546,6 +2654,8 @@ func fillValueTV(store Store, tv *TypedValue) *TypedValue { case *Block: vpv := cb.GetPointerToInt(store, cv.Index) cv.TV = vpv.TV // TODO optimize? + case *HeapItemValue: + cv.TV = &cb.Value default: panic("should not happen") } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index b4888878c7a..c5ddc232fcb 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -881,6 +881,11 @@ GNO_CASE: // Panics if conversion is illegal. // TODO: method on TypedValue? func ConvertUntypedTo(tv *TypedValue, t Type) { + if debug { + defer func() { + debug.Printf("ConvertUntypedTo done, tv: %v \n", tv) + }() + } if debug { if !isUntyped(tv.T) { panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index ad6e7bfc854..204fab62c86 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -258,6 +258,11 @@ func (v RefValue) String() string { v.PkgPath) } +func (v *HeapItemValue) String() string { + return fmt.Sprintf("heapitem(%v)", + v.Value) +} + // ---------------------------------------- // *TypedValue.Sprint @@ -376,7 +381,7 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo default: // The remaining types may have a nil value. if tv.V == nil { - return nilStr + " " + tv.T.String() + return "(" + nilStr + " " + tv.T.String() + ")" } // *ArrayType, *SliceType, *StructType, *MapType diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index fda9263914e..b6ee95acac8 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/transpiler" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) @@ -183,13 +183,8 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { if strings.HasSuffix(path, modFile.Module.Mod.Path) { continue } - // skip if `std`, special case. - if path == transpiler.GnoStdPkgAfter { - continue - } - if strings.HasPrefix(path, transpiler.ImportPrefix) { - path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") + if !gno.IsStdlib(path) { modFile.AddNewRequire(path, "v0.0.0-latest", true) } } diff --git a/gnovm/pkg/gnomod/file_test.go b/gnovm/pkg/gnomod/file_test.go index 0012960eb4f..7abfe16f340 100644 --- a/gnovm/pkg/gnomod/file_test.go +++ b/gnovm/pkg/gnomod/file_test.go @@ -14,7 +14,7 @@ import ( "golang.org/x/mod/module" ) -const testRemote string = "test3.gno.land:36657" +const testRemote string = "test3.gno.land:26657" func TestFetchDeps(t *testing.T) { for _, tc := range []struct { diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index af946f2bf39..0effa532107 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -3,6 +3,8 @@ package gnomod import ( "errors" "fmt" + "go/parser" + gotoken "go/token" "os" "path/filepath" "strings" @@ -63,23 +65,12 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err } else { // Is File // Transpile and write generated go file - if strings.HasSuffix(fileName, ".gno") { - filePath := filepath.Join(basePath, pkgPath) - targetFilename, _ := transpiler.GetTranspileFilenameAndTags(filePath) - transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) - if err != nil { - return nil, fmt.Errorf("transpile: %w", err) - } - - for _, i := range transpileRes.Imports { - requirements = append(requirements, i.Path.Value) - } - - targetFileNameWithPath := filepath.Join(basePath, dirPath, targetFilename) - err = os.WriteFile(targetFileNameWithPath, []byte(transpileRes.Translated), 0o644) - if err != nil { - return nil, fmt.Errorf("writefile %q: %w", targetFileNameWithPath, err) - } + file, err := parser.ParseFile(gotoken.NewFileSet(), fileName, res.Data, parser.ImportsOnly) + if err != nil { + return nil, fmt.Errorf("parse gno file: %w", err) + } + for _, i := range file.Imports { + requirements = append(requirements, i.Path.Value) } // Write file @@ -96,11 +87,12 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err // GnoToGoMod make necessary modifications in the gno.mod // and return go.mod file. func GnoToGoMod(f File) (*File, error) { + // TODO(morgan): good candidate to move to pkg/transpiler. + gnoModPath := GetGnoModPath() - if strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoPackagePrefixBefore) { - f.AddModuleStmt(transpiler.ImportPrefix + "/examples/" + f.Module.Mod.Path) + if !gnolang.IsStdlib(f.Module.Mod.Path) { + f.AddModuleStmt(transpiler.TranspileImportPath(f.Module.Mod.Path)) } for i := range f.Require { @@ -111,14 +103,13 @@ func GnoToGoMod(f File) (*File, error) { } } path := f.Require[i].Mod.Path - if strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoPackagePrefixBefore) { + if !gnolang.IsStdlib(path) { // Add dependency with a modified import path - f.AddRequire(transpiler.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) + f.AddRequire(transpiler.TranspileImportPath(path), f.Require[i].Mod.Version) } - f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") + f.AddReplace(path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") // Remove the old require since the new dependency was added above - f.DropRequire(f.Require[i].Mod.Path) + f.DropRequire(path) } // Remove replacements that are not replaced by directories. diff --git a/gnovm/pkg/integration/gno.go b/gnovm/pkg/integration/gno.go index ee0216fa9e8..a389b6a9b24 100644 --- a/gnovm/pkg/integration/gno.go +++ b/gnovm/pkg/integration/gno.go @@ -68,6 +68,8 @@ func SetupGno(p *testscript.Params, buildDir string) error { return fmt.Errorf("unable to create temporary home directory: %w", err) } env.Setenv("HOME", home) + // Avoids go command printing errors relating to lack of go.mod. + env.Setenv("GO111MODULE", "off") // Cleanup home folder env.Defer(func() { os.RemoveAll(home) }) diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index c7786cf08b0..e7b5ecea96f 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -10,6 +10,7 @@ import ( "go/printer" "go/token" "io" + "os" "text/template" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -106,6 +107,7 @@ type Repl struct { stdout io.Writer stderr io.Writer stdin io.Reader + debug bool } // NewRepl creates a Repl struct. It is able to process input source code and eventually run it. @@ -151,6 +153,14 @@ 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 { + return r.state.files[file] + }) + r.debug = false + defer r.state.machine.Debugger.Disable() + } + decl, declErr := r.parseDeclaration(input) if declErr == nil { return r.handleDeclarations(decl) @@ -317,3 +327,8 @@ func (r *Repl) Src() string { return b.String() } + +// Debug activates the GnoVM debugger for the next evaluation. +func (r *Repl) Debug() { + r.debug = true +} diff --git a/gnovm/pkg/repl/repl_test.go b/gnovm/pkg/repl/repl_test.go index fbb2efe8890..58b75188dde 100644 --- a/gnovm/pkg/repl/repl_test.go +++ b/gnovm/pkg/repl/repl_test.go @@ -69,7 +69,7 @@ var fixtures = []struct { CodeSteps: []step{ { Line: "importasdasd", - Error: "test/test1.gno:7: name importasdasd not declared", + Error: "test/test1.gno:7:2: name importasdasd not declared", }, { Line: "var a := 1", diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index cc49aac4c78..bd4bb1b1bc9 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -1,3 +1,5 @@ +// Package transpiler implements a source-to-source compiler for translating Gno +// code into Go code. package transpiler import ( @@ -9,102 +11,52 @@ import ( goscanner "go/scanner" "go/token" "os" - "os/exec" + "path" "path/filepath" - "regexp" - "sort" "strconv" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" "golang.org/x/tools/go/ast/astutil" ) -const ( - GnoRealmPkgsPrefixBefore = "gno.land/r/" - GnoRealmPkgsPrefixAfter = "github.com/gnolang/gno/examples/gno.land/r/" - GnoPackagePrefixBefore = "gno.land/p/demo/" - GnoPackagePrefixAfter = "github.com/gnolang/gno/examples/gno.land/p/demo/" - GnoStdPkgBefore = "std" - GnoStdPkgAfter = "github.com/gnolang/gno/gnovm/stdlibs/stdshim" -) - -var stdlibWhitelist = []string{ - // go - "bufio", - "bytes", - "compress/gzip", - "context", - "crypto/md5", - "crypto/sha1", - "crypto/chacha20", - "crypto/cipher", - "crypto/sha256", - "encoding/base64", - "encoding/binary", - "encoding/hex", - "encoding/json", - "encoding/xml", - "errors", - "hash", - "hash/adler32", - "internal/bytealg", - "internal/os", - "flag", - "fmt", - "io", - "io/util", - "math", - "math/big", - "math/bits", - "math/rand", - "net/url", - "path", - "regexp", - "sort", - "strconv", - "strings", - "text/template", - "time", - "unicode", - "unicode/utf8", - "unicode/utf16", +// ImportPrefix is the import path to the root of the gno repository, which should +// be used to create go import paths. +const ImportPrefix = "github.com/gnolang/gno" - // gno - "std", +// TranspileImportPath takes an import path s, and converts it into the full +// import path relative to the Gno repository. +func TranspileImportPath(s string) string { + return ImportPrefix + "/" + PackageDirLocation(s) } -var importPrefixWhitelist = []string{ - "github.com/gnolang/gno/_test", +// PackageDirLocation provides the supposed directory of the package, relative to the root dir. +// +// TODO(morgan): move out, this should go in a "resolver" package. +func PackageDirLocation(s string) string { + switch { + case !gno.IsStdlib(s): + return "examples/" + s + default: + return "gnovm/stdlibs/" + s + } } -const ImportPrefix = "github.com/gnolang/gno" - -type transpileResult struct { +// Result is returned by Transpile, returning the file's imports and output +// out the transpilation. +type Result struct { Imports []*ast.ImportSpec Translated string + File *ast.File } // TODO: func TranspileFile: supports caching. // TODO: func TranspilePkg: supports directories. -func guessRootDir(fileOrPkg string, goBinary string) (string, error) { - abs, err := filepath.Abs(fileOrPkg) - if err != nil { - return "", err - } - args := []string{"list", "-m", "-mod=mod", "-f", "{{.Dir}}", ImportPrefix} - cmd := exec.Command(goBinary, args...) - cmd.Dir = abs - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("can't guess --root-dir") - } - rootDir := strings.TrimSpace(string(out)) - return rootDir, nil -} - -// GetTranspileFilenameAndTags returns the filename and tags for transpiled files. -func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { +// TranspiledFilenameAndTags returns the filename and tags for transpiled files. +func TranspiledFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { nameNoExtension := strings.TrimSuffix(filepath.Base(gnoFilePath), ".gno") switch { case strings.HasSuffix(gnoFilePath, "_filetest.gno"): @@ -120,17 +72,43 @@ func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags strin return } -func Transpile(source string, tags string, filename string) (*transpileResult, error) { +// Transpile performs transpilation on the given source code. tags can be used +// to specify build tags; and filename helps generate useful error messages and +// discriminate between test and normal source files. +func Transpile(source, tags, filename string) (*Result, error) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) + f, err := parser.ParseFile(fset, filename, source, + // SkipObjectResolution -- unused here. + // ParseComments -- so that they show up when re-building the AST. + parser.SkipObjectResolution|parser.ParseComments) if err != nil { return nil, fmt.Errorf("parse: %w", err) } isTestFile := strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") - shouldCheckWhitelist := !isTestFile + ctx := &transpileCtx{ + rootDir: gnoenv.RootDir(), + } + stdlibPrefix := filepath.Join(ctx.rootDir, "gnovm", "stdlibs") + if isTestFile { + // XXX(morgan): this disables checking that a package exists (in examples or stdlibs) + // when transpiling a test file. After all Gno functions, including those in + // tests/imports.go are converted to native bindings, support should + // be added for transpiling stdlibs only available in tests/stdlibs, and + // enable as such "package checking" also on test files. + ctx.rootDir = "" + } + if strings.HasPrefix(filename, stdlibPrefix) { + // this is a standard library. Mark it in the options so the native + // bindings resolve correctly. + path := strings.TrimPrefix(filename, stdlibPrefix) + path = filepath.ToSlash(filepath.Dir(path)) + path = strings.TrimLeft(path, "/") + + ctx.stdlibPath = path + } - transformed, err := transpileAST(fset, f, shouldCheckWhitelist) + transformed, err := ctx.transformFile(fset, f) if err != nil { return nil, fmt.Errorf("transpileAST: %w", err) } @@ -152,190 +130,64 @@ func Transpile(source string, tags string, filename string) (*transpileResult, e return nil, fmt.Errorf("format.Node: %w", err) } - res := &transpileResult{ + res := &Result{ Imports: f.Imports, Translated: out.String(), + File: transformed, } return res, nil } -// TranspileVerifyFile tries to run `go fmt` against a transpiled .go file. -// -// This is fast and won't look the imports. -func TranspileVerifyFile(path string, gofmtBinary string) error { - // TODO: use cmd/parser instead of exec? +type transpileCtx struct { + // If rootDir is given, we will check that the directory of the import path + // exists (using rootDir/packageDirLocation()). + rootDir string + // This should be set if we're working with a file from a standard library. + // This allows us to easily check if a function has a native binding, and as + // such modify its call expressions appropriately. + stdlibPath string - args := strings.Split(gofmtBinary, " ") - args = append(args, []string{"-l", "-e", path}...) - cmd := exec.Command(args[0], args[1:]...) - out, err := cmd.CombinedOutput() - if err != nil { - fmt.Fprintln(os.Stderr, string(out)) - return fmt.Errorf("%s: %w", gofmtBinary, err) - } - return nil -} - -// TranspileBuildPackage tries to run `go build` against the transpiled .go files. -// -// This method is the most efficient to detect errors but requires that -// all the import are valid and available. -func TranspileBuildPackage(fileOrPkg, goBinary string) error { - // TODO: use cmd/compile instead of exec? - // TODO: find the nearest go.mod file, chdir in the same folder, rim prefix? - // TODO: temporarily create an in-memory go.mod or disable go modules for gno? - // TODO: ignore .go files that were not generated from gno? - // TODO: automatically transpile if not yet done. - - files := []string{} - - info, err := os.Stat(fileOrPkg) - if err != nil { - return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) - } - if !info.IsDir() { - file := fileOrPkg - files = append(files, file) - } else { - pkgDir := fileOrPkg - goGlob := filepath.Join(pkgDir, "*.go") - goMatches, err := filepath.Glob(goGlob) - if err != nil { - return fmt.Errorf("glob %s: %w", goGlob, err) - } - for _, goMatch := range goMatches { - switch { - case strings.HasPrefix(goMatch, "."): // skip - case strings.HasSuffix(goMatch, "_filetest.go"): // skip - case strings.HasSuffix(goMatch, "_filetest.gno.gen.go"): // skip - case strings.HasSuffix(goMatch, "_test.go"): // skip - case strings.HasSuffix(goMatch, "_test.gno.gen.go"): // skip - default: - files = append(files, goMatch) - } - } - } - - sort.Strings(files) - args := append([]string{"build", "-v", "-tags=gno"}, files...) - cmd := exec.Command(goBinary, args...) - rootDir, err := guessRootDir(fileOrPkg, goBinary) - if err == nil { - cmd.Dir = rootDir - } - out, err := cmd.CombinedOutput() - if _, ok := err.(*exec.ExitError); ok { - // exit error - return parseGoBuildErrors(string(out)) - } - return err -} - -var errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) - -// parseGoBuildErrors returns a scanner.ErrorList filled with all errors found -// in out, which is supposed to be the output of the `go build` command. -// -// TODO(tb): update when `go build -json` is released to replace regexp usage. -// See https://github.com/golang/go/issues/62067 -func parseGoBuildErrors(out string) error { - var errList goscanner.ErrorList - matches := errorRe.FindAllStringSubmatch(out, -1) - for _, match := range matches { - filename := match[1] - line, err := strconv.Atoi(match[2]) - if err != nil { - return fmt.Errorf("parse line go build error %s: %w", match, err) - } - - column, err := strconv.Atoi(match[3]) - if err != nil { - return fmt.Errorf("parse column go build error %s: %w", match, err) - } - msg := match[4] - errList.Add(token.Position{ - Filename: filename, - Line: line, - Column: column, - }, msg) - } - return errList.Err() + stdlibImports map[string]string // symbol -> import path } -func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { +func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (*ast.File, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) + ctx.stdlibImports = make(map[string]string) - // import whitelist - if checkWhitelist { - for _, paragraph := range imports { - for _, importSpec := range paragraph { - importPath := strings.TrimPrefix(strings.TrimSuffix(importSpec.Path.Value, `"`), `"`) - - if strings.HasPrefix(importPath, GnoRealmPkgsPrefixBefore) { - continue - } - - if strings.HasPrefix(importPath, GnoPackagePrefixBefore) { - continue - } - - valid := false - for _, whitelisted := range stdlibWhitelist { - if importPath == whitelisted { - valid = true - break - } - } - if valid { - continue - } - - for _, whitelisted := range importPrefixWhitelist { - if strings.HasPrefix(importPath, whitelisted) { - valid = true - break - } - } - if valid { - continue - } - - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("import %q is not in the whitelist", importPath)) - } - } - } - - // rewrite imports + // rewrite imports to point to stdlibs/ or examples/ for _, paragraph := range imports { for _, importSpec := range paragraph { - importPath := strings.TrimPrefix(strings.TrimSuffix(importSpec.Path.Value, `"`), `"`) - - // std package - if importPath == GnoStdPkgBefore { - if !astutil.RewriteImport(fset, f, GnoStdPkgBefore, GnoStdPkgAfter) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", GnoStdPkgBefore, GnoStdPkgAfter)) - } + importPath, err := strconv.Unquote(importSpec.Path.Value) + if err != nil { + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("can't unquote import path %s: %v", importSpec.Path.Value, err)) + continue } - // p/pkg packages - if strings.HasPrefix(importPath, GnoPackagePrefixBefore) { - target := GnoPackagePrefixAfter + strings.TrimPrefix(importPath, GnoPackagePrefixBefore) - - if !astutil.RewriteImport(fset, f, importPath, target) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, target)) + if ctx.rootDir != "" { + dirPath := filepath.Join(ctx.rootDir, PackageDirLocation(importPath)) + if _, err := os.Stat(dirPath); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("import %q does not exist", importPath)) + continue } } - // r/realm packages - if strings.HasPrefix(importPath, GnoRealmPkgsPrefixBefore) { - target := GnoRealmPkgsPrefixAfter + strings.TrimPrefix(importPath, GnoRealmPkgsPrefixBefore) - - if !astutil.RewriteImport(fset, f, importPath, target) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, target)) + // Create mapping + if gno.IsStdlib(importPath) { + if importSpec.Name != nil { + ctx.stdlibImports[importSpec.Name.Name] = importPath + } else { + // XXX: imperfect, see comment on transformCallExpr + ctx.stdlibImports[path.Base(importPath)] = importPath } } + + transp := TranspileImportPath(importPath) + importSpec.Path.Value = strconv.Quote(transp) } } @@ -343,14 +195,72 @@ func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.No node := astutil.Apply(f, // pre func(c *astutil.Cursor) bool { - // do things here + node := c.Node() + // is function declaration without body? + // -> delete (native binding) + if fd, ok := node.(*ast.FuncDecl); ok && fd.Body == nil { + c.Delete() + return false // don't attempt to traverse children + } + + // is function call to a native function? + // -> rename if unexported, apply `nil,` for the first arg if necessary + if ce, ok := node.(*ast.CallExpr); ok { + return ctx.transformCallExpr(c, ce) + } + return true }, + // post func(c *astutil.Cursor) bool { - // and here return true }, ) - return node, errs.Err() + return node.(*ast.File), errs.Err() +} + +func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) bool { + switch fe := ce.Fun.(type) { + case *ast.SelectorExpr: + // XXX: This is not correct in 100% of cases. If I shadow the `std` symbol, and + // its replacement is a type with the method AssertOriginCall, this system + // will incorrectly add a `nil` as the first argument. + // A full fix requires understanding scope; the Go standard library recommends + // using go/types, which for proper functioning requires an importer + // which can work with Gno. This is deferred for a future PR. + id, ok := fe.X.(*ast.Ident) + if !ok { + break + } + ip, ok := ctx.stdlibImports[id.Name] + if !ok { + break + } + nat := stdlibs.FindNative(ip, gno.Name(fe.Sel.Name)) + if nat != nil && nat.HasMachineParam() { + // Because it's an import, the symbol is always exported, so no need for the + // X_ prefix we add below. + ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) + } + + case *ast.Ident: + // Is this a native binding? + // Note: this is only useful within packages like `std` and `math`. + // The logic here is not robust to be generic. It does not account for locally + // defined scope. However, because native bindings have a narrowly defined and + // controlled scope (standard libraries) this will work for our usecase. + nat := stdlibs.FindNative(ctx.stdlibPath, gno.Name(fe.Name)) + if ctx.stdlibPath != "" && nat != nil { + if nat.HasMachineParam() { + ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) + } + if !fe.IsExported() { + // Prefix unexported names with X_, per native binding convention + // (to export the symbol within Go). + fe.Name = "X_" + fe.Name + } + } + } + return true } diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 81ea4b0dfcc..2a0707f7f79 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -2,21 +2,69 @@ package transpiler import ( "go/ast" - goscanner "go/scanner" - "go/token" + "path/filepath" "strings" "testing" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestTranspiledFilenameAndTags(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + changed string + tags string + }{ + { + "hello.gno", + "hello.gno.gen.go", + "gno", + }, + { + "a/b/hello.gno", + "hello.gno.gen.go", + "gno", + }, + { + "hey_test.gno", + ".hey_test.gno.gen_test.go", + "gno && test", + }, + { + "hey_filetest.gno", + ".hey_filetest.gno.gen.go", + "gno && filetest", + }, + { + "badname.go", + "badname.go.gno.gen.go", + "gno", + }, + { + "badname_test.go", + "badname_test.go.gno.gen.go", + "gno", + }, + } + + for _, tc := range tt { + newName, tags := TranspiledFilenameAndTags(tc.name) + assert.Equal(t, tc.changed, newName, "name for %q", tc.name) + assert.Equal(t, tc.tags, tags, "tags for %q", tc.name) + } +} + func TestTranspile(t *testing.T) { t.Parallel() cases := []struct { name string tags string + filename string source string expectedOutput string expectedImports []*ast.ImportSpec @@ -75,7 +123,7 @@ func hello() string { //line foo.gno:1:1 package foo -import "github.com/gnolang/gno/gnovm/stdlibs/stdshim" +import "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() string { _ = std.Foo @@ -87,9 +135,8 @@ func hello() string { Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"github.com/gnolang/gno/gnovm/stdlibs/stdshim"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, }, - EndPos: 26, }, }, }, @@ -98,7 +145,7 @@ func hello() string { source: ` package foo -import "gno.land/r/users" +import "gno.land/r/demo/users" func foo() { _ = users.Register} `, @@ -108,7 +155,7 @@ func foo() { _ = users.Register} //line foo.gno:1:1 package foo -import "github.com/gnolang/gno/examples/gno.land/r/users" +import "github.com/gnolang/gno/examples/gno.land/r/demo/users" func foo() { _ = users.Register } `, @@ -117,9 +164,8 @@ func foo() { _ = users.Register } Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"github.com/gnolang/gno/examples/gno.land/r/users"`, + Value: `"github.com/gnolang/gno/examples/gno.land/r/demo/users"`, }, - EndPos: 39, }, }, }, @@ -130,7 +176,7 @@ package foo import "gno.land/p/demo/avl" -func foo() { _ = avl.Tree } +func foo() { _ = avl.NewTree("hey", 1) } `, expectedOutput: ` // Code generated by github.com/gnolang/gno. DO NOT EDIT. @@ -140,7 +186,7 @@ package foo import "github.com/gnolang/gno/examples/gno.land/p/demo/avl" -func foo() { _ = avl.Tree } +func foo() { _ = avl.NewTree("hey", 1) } `, expectedImports: []*ast.ImportSpec{ { @@ -149,7 +195,6 @@ func foo() { _ = avl.Tree } Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/p/demo/avl"`, }, - EndPos: 42, }, }, }, @@ -171,7 +216,7 @@ func hello() string { //line foo.gno:1:1 package foo -import bar "github.com/gnolang/gno/gnovm/stdlibs/stdshim" +import bar "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() string { _ = bar.Foo @@ -187,14 +232,13 @@ func hello() string { Path: &ast.BasicLit{ ValuePos: 25, Kind: 9, - Value: `"github.com/gnolang/gno/gnovm/stdlibs/stdshim"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, }, - EndPos: 30, }, }, }, { - name: "blacklisted-package", + name: "unknown-package", source: ` package foo @@ -202,7 +246,7 @@ import "reflect" func foo() { _ = reflect.ValueOf } `, - expectedError: `transpileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, + expectedError: `transpileAST: foo.gno:3:8: import "reflect" does not exist`, }, { name: "syntax-error", @@ -226,6 +270,27 @@ import "gno.land/p/demo/unknownxyz" //line foo.gno:1:1 package foo +import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" +`, + expectedError: `transpileAST: foo.gno:3:8: import "gno.land/p/demo/unknownxyz" does not exist`, + }, + { + // Test files should allow unknown imports while + // we still have "native" packages. + + name: "unknown-realm-test", + filename: "foo_test.gno", + source: ` +package foo + +import "gno.land/p/demo/unknownxyz" +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line foo_test.gno:1:1 +package foo + import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" `, expectedImports: []*ast.ImportSpec{ @@ -235,12 +300,11 @@ import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz"`, }, - EndPos: 49, }, }, }, { - name: "whitelisted-package", + name: "imported-package", source: ` package foo @@ -254,7 +318,7 @@ func foo() { _ = regexp.MatchString } //line foo.gno:1:1 package foo -import "regexp" +import "github.com/gnolang/gno/gnovm/stdlibs/regexp" func foo() { _ = regexp.MatchString } `, @@ -263,11 +327,87 @@ func foo() { _ = regexp.MatchString } Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"regexp"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/regexp"`, }, }, }, }, + { + name: "natbind-func", + filename: filepath.Join(gnoenv.RootDir(), "gnovm/stdlibs/math/math.gno"), + source: ` +package math + +import "std" + +func Float32bits(i float32) uint32 + +func testfunc() { + println(Float32bits(3.14159)) + std.AssertOriginCall() +} + +func otherFunc() { + std := 1 + // This is (incorrectly) changed for now. + std.AssertOriginCall() +} +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line math.gno:1:1 +package math + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func testfunc() { + println(Float32bits(3.14159)) + std.AssertOriginCall(nil) +} + +func otherFunc() { + std := 1 + // This is (incorrectly) changed for now. + std.AssertOriginCall(nil) +} +`, + expectedImports: []*ast.ImportSpec{ + { + Path: &ast.BasicLit{ + ValuePos: 22, + Kind: 9, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, + }, + }, + }, + }, + { + name: "natbind-std", + filename: filepath.Join(gnoenv.RootDir(), "gnovm/stdlibs/std/std.gno"), + source: ` +package std + +func AssertOriginCall() +func origCaller() string + +func testfunc() { + AssertOriginCall() + println(origCaller()) +} +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line std.gno:1:1 +package std + +func testfunc() { + AssertOriginCall(nil) + println(X_origCaller(nil)) +} +`, + }, } for _, c := range cases { c := c // scopelint @@ -276,7 +416,11 @@ func foo() { _ = regexp.MatchString } // "\n" is added for better test case readability, now trim it source := strings.TrimPrefix(c.source, "\n") - res, err := Transpile(source, c.tags, "foo.gno") + filename := c.filename + if filename == "" { + filename = "foo.gno" + } + res, err := Transpile(source, c.tags, filename) if c.expectedError != "" { require.EqualError(t, err, c.expectedError) @@ -289,53 +433,3 @@ func foo() { _ = regexp.MatchString } }) } } - -func TestParseGoBuildErrors(t *testing.T) { - tests := []struct { - name string - output string - expectedError error - }{ - { - name: "empty output", - output: "", - expectedError: nil, - }, - { - name: "random output", - output: "xxx", - expectedError: nil, - }, - { - name: "some errors", - output: `xxx -main.gno:6:2: nasty error -pkg/file.gno:60:20: ugly error`, - expectedError: goscanner.ErrorList{ - &goscanner.Error{ - Pos: token.Position{ - Filename: "main.gno", - Line: 6, - Column: 2, - }, - Msg: "nasty error", - }, - &goscanner.Error{ - Pos: token.Position{ - Filename: "pkg/file.gno", - Line: 60, - Column: 20, - }, - Msg: "ugly error", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := parseGoBuildErrors(tt.output) - - assert.Equal(t, tt.expectedError, err) - }) - } -} diff --git a/gnovm/stdlibs/bytes/boundary_test.gno b/gnovm/stdlibs/bytes/boundary_test.gno index 9873b1db987..5d77c576ff8 100644 --- a/gnovm/stdlibs/bytes/boundary_test.gno +++ b/gnovm/stdlibs/bytes/boundary_test.gno @@ -1,8 +1,6 @@ // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// -//go:build linux package bytes_test diff --git a/gnovm/stdlibs/bytes/buffer_test.gno b/gnovm/stdlibs/bytes/buffer_test.gno index a8837494224..601901955cd 100644 --- a/gnovm/stdlibs/bytes/buffer_test.gno +++ b/gnovm/stdlibs/bytes/buffer_test.gno @@ -216,14 +216,14 @@ func TestMixedReadsAndWrites(t *testing.T) { var buf bytes.Buffer s := "" for i := 0; i < 50; i++ { - wlen := rand.Intn(len(testString)) + wlen := rand.IntN(len(testString)) if i%2 == 0 { s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testString[0:wlen]) } else { s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen]) } - rlen := rand.Intn(len(testString)) + rlen := rand.IntN(len(testString)) fub := make([]byte, rlen) n, _ := buf.Read(fub) s = s[n:] diff --git a/gnovm/stdlibs/bytes/bytes_test.gno b/gnovm/stdlibs/bytes/bytes_test.gno index c7762f2f67b..927f89c5559 100644 --- a/gnovm/stdlibs/bytes/bytes_test.gno +++ b/gnovm/stdlibs/bytes/bytes_test.gno @@ -1707,7 +1707,7 @@ var makeFieldsInput = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space. for i := range x { - switch rand.Intn(10) { + switch rand.IntN(10) { case 0: x[i] = ' ' case 1: @@ -1729,7 +1729,7 @@ var makeFieldsInputASCII = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, rest ASCII non-space. for i := range x { - if rand.Intn(10) == 0 { + if rand.IntN(10) == 0 { x[i] = ' ' } else { x[i] = 'x' @@ -1827,7 +1827,7 @@ func makeBenchInputHard() []byte { } x := make([]byte, 0, 1<<20) for { - i := rand.Intn(len(tokens)) + i := rand.IntN(len(tokens)) if len(x)+len(tokens[i]) >= 1<<20 { break } diff --git a/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno b/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno index 903e2653572..91e5481fc42 100644 --- a/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno +++ b/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a license that can be // found in the LICENSE file. -//go:build (!amd64 && !386) || gccgo || appengine || nacl - package chacha import "encoding/binary" diff --git a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno index 419c3bc61a7..615ea0eb6a7 100644 --- a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno +++ b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno @@ -3,7 +3,6 @@ package ed25519 import ( "crypto/ed25519" "encoding/hex" - "std" "testing" ) diff --git a/gnovm/stdlibs/crypto/sha256/sha256_test.gno b/gnovm/stdlibs/crypto/sha256/sha256_test.gno index 2522d822307..26d96cd547e 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256_test.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256_test.gno @@ -3,7 +3,6 @@ package sha256 import ( "crypto/sha256" "encoding/hex" - "std" "testing" ) diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/generated.go similarity index 91% rename from gnovm/stdlibs/native.go rename to gnovm/stdlibs/generated.go index 3b1ab719e72..0af38950ce1 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/generated.go @@ -16,15 +16,25 @@ import ( libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" ) -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ { "crypto/ed25519", "verify", @@ -36,6 +46,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -69,6 +80,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[32]byte")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -96,6 +108,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("uint32")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -123,6 +136,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("float32")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -150,6 +164,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("uint64")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -177,6 +192,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("float64")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -206,6 +222,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("[]string")}, {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -245,6 +262,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p4"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -281,6 +299,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -314,6 +333,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -347,6 +367,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -378,6 +399,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p1"), Type: gno.X("[]string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -400,6 +422,7 @@ var nativeFuncs = [...]nativeFunc{ "AssertOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { libs_std.AssertOriginCall( m, @@ -413,6 +436,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + true, func(m *gno.Machine) { r0 := libs_std.IsOriginCall( m, @@ -425,25 +449,6 @@ var nativeFuncs = [...]nativeFunc{ )) }, }, - { - "std", - "CurrentRealmPath", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - }, - func(m *gno.Machine) { - r0 := libs_std.CurrentRealmPath( - m, - ) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, { "std", "GetChainID", @@ -451,6 +456,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.GetChainID( m, @@ -470,6 +476,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { r0 := libs_std.GetHeight( m, @@ -490,6 +497,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("[]string")}, {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, + true, func(m *gno.Machine) { r0, r1 := libs_std.X_origSend( m, @@ -514,6 +522,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.X_origCaller( m, @@ -533,6 +542,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.X_origPkgAddr( m, @@ -554,6 +564,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -584,6 +595,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("string")}, {Name: gno.N("r1"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -618,6 +630,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -646,6 +659,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -678,6 +692,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r1"), Type: gno.X("[20]byte")}, {Name: gno.N("r2"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -715,6 +730,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -744,6 +760,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[]byte")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -778,6 +795,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("int")}, {Name: gno.N("r1"), Type: gno.X("error")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -810,6 +828,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -838,6 +857,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -869,6 +889,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -899,6 +920,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -926,6 +948,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -951,6 +974,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + false, func(m *gno.Machine) { r0 := libs_testing.X_unixNano() @@ -970,6 +994,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r1"), Type: gno.X("int32")}, {Name: gno.N("r2"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { r0, r1, r2 := libs_time.X_now( m, @@ -993,3 +1018,51 @@ var nativeFuncs = [...]nativeFunc{ }, }, } + +var initOrder = [...]string{ + "errors", + "internal/bytealg", + "io", + "unicode", + "unicode/utf8", + "bytes", + "strings", + "bufio", + "encoding/binary", + "math/bits", + "math", + "crypto/chacha20/chacha", + "crypto/cipher", + "crypto/chacha20", + "strconv", + "crypto/chacha20/rand", + "crypto/ed25519", + "crypto/sha256", + "encoding", + "encoding/base64", + "encoding/hex", + "hash", + "hash/adler32", + "math/overflow", + "math/rand", + "path", + "sort", + "net/url", + "regexp/syntax", + "regexp", + "std", + "testing", + "time", + "unicode/utf16", +} + +// InitOrder returns the initialization order of the standard libraries. +// This is calculated starting from the list of all standard libraries and +// iterating through each: if a package depends on an unitialized package, that +// is processed first, and so on recursively; matching the behaviour of Go's +// [program initialization]. +// +// [program initialization]: https://go.dev/ref/spec#Program_initialization +func InitOrder() []string { + return initOrder[:] +} diff --git a/gnovm/stdlibs/hash/marshal_test.gno b/gnovm/stdlibs/hash/marshal_test.gno index b31d35faa77..bf823d97cce 100644 --- a/gnovm/stdlibs/hash/marshal_test.gno +++ b/gnovm/stdlibs/hash/marshal_test.gno @@ -10,9 +10,6 @@ package hash import ( "bytes" - "crypto/md5" - "crypto/sha1" - "crypto/sha256" "encoding" "encoding/hex" "hash" diff --git a/gnovm/stdlibs/internal/bytealg/compare_generic.gno b/gnovm/stdlibs/internal/bytealg/compare_generic.gno index e1795e47e9a..b56d0a67e02 100644 --- a/gnovm/stdlibs/internal/bytealg/compare_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/compare_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !386 && !amd64 && !s390x && !arm && !arm64 && !ppc64 && !ppc64le && !mips && !mipsle && !wasm && !mips64 && !mips64le - package bytealg // import _ "unsafe" // for go:linkname @@ -35,7 +33,6 @@ samebytes: return 0 } -//go:linkname runtime_cmpstring runtime.cmpstring func runtime_cmpstring(a, b string) int { l := len(a) if len(b) < l { diff --git a/gnovm/stdlibs/internal/bytealg/count_generic.gno b/gnovm/stdlibs/internal/bytealg/count_generic.gno index 932a7c584c1..de08418fcaa 100644 --- a/gnovm/stdlibs/internal/bytealg/count_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/count_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !amd64 && !arm && !arm64 && !ppc64le && !ppc64 && !riscv64 && !s390x - package bytealg func Count(b []byte, c byte) int { diff --git a/gnovm/stdlibs/internal/bytealg/index_generic.gno b/gnovm/stdlibs/internal/bytealg/index_generic.gno index a59e32938e7..d751b1bc940 100644 --- a/gnovm/stdlibs/internal/bytealg/index_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/index_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !amd64 && !arm64 && !s390x && !ppc64le && !ppc64 - package bytealg const MaxBruteForce = 0 diff --git a/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno b/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno index 0a45f903843..47aee225df9 100644 --- a/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !386 && !amd64 && !s390x && !arm && !arm64 && !ppc64 && !ppc64le && !mips && !mipsle && !mips64 && !mips64le && !riscv64 && !wasm - package bytealg func IndexByte(b []byte, c byte) int { diff --git a/gnovm/stdlibs/io/io_test.gno b/gnovm/stdlibs/io/io_test.gno index 613b7d13e35..a7533a87799 100644 --- a/gnovm/stdlibs/io/io_test.gno +++ b/gnovm/stdlibs/io/io_test.gno @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "os" "strings" "testing" ) diff --git a/gnovm/stdlibs/math/rand/auto_test.gno b/gnovm/stdlibs/math/rand/auto_test.gno new file mode 100644 index 00000000000..5945039ae18 --- /dev/null +++ b/gnovm/stdlibs/math/rand/auto_test.gno @@ -0,0 +1,39 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "testing" +) + +// This test is first, in its own file with an alphabetically early name, +// to try to make sure that it runs early. It has the best chance of +// detecting deterministic seeding if it's the first test that runs. + +func TestAuto(t *testing.T) { + // Pull out 10 int64s from the global source + // and then check that they don't appear in that + // order in the deterministic seeded result. + var out []int64 + for i := 0; i < 10; i++ { + out = append(out, Int64()) + } + + // Look for out in seeded output. + // Strictly speaking, we should look for them in order, + // but this is good enough and not significantly more + // likely to have a false positive. + r := New(NewPCG(1, 0)) + found := 0 + for i := 0; i < 1000; i++ { + x := r.Int64() + if x == out[found] { + found++ + if found == len(out) { + t.Fatalf("found unseeded output in Seed(1) output") + } + } + } +} diff --git a/gnovm/stdlibs/math/rand/example_test.gno b/gnovm/stdlibs/math/rand/example_test.gno new file mode 100644 index 00000000000..83da46b2a3c --- /dev/null +++ b/gnovm/stdlibs/math/rand/example_test.gno @@ -0,0 +1,129 @@ +// 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 rand_test + +import ( + "fmt" + "math/rand" + "strings" +) + +// These tests serve as an example but also make sure we don't change +// the output of the random number generator when given a fixed seed. + +func Example() { + answers := []string{ + "It is certain", + "It is decidedly so", + "Without a doubt", + "Yes definitely", + "You may rely on it", + "As I see it yes", + "Most likely", + "Outlook good", + "Yes", + "Signs point to yes", + "Reply hazy try again", + "Ask again later", + "Better not tell you now", + "Cannot predict now", + "Concentrate and ask again", + "Don't count on it", + "My reply is no", + "My sources say no", + "Outlook not so good", + "Very doubtful", + } + fmt.Println("Magic 8-Ball says:", answers[rand.IntN(len(answers))]) +} + +// This example shows the use of each of the methods on a *Rand. +// The use of the global functions is the same, without the receiver. +func Example_rand() { + // Create and seed the generator. + // Typically a non-fixed seed should be used, such as Uint64(), Uint64(). + // Using a fixed seed will produce the same output on every run. + 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{}) { + fmt.Printf("%s\t%v\t%v\t%v\n", name, v1, v2, v3) + } + + // Float32 and Float64 values are in [0, 1). + show("Float32", r.Float32(), r.Float32(), r.Float32()) + show("Float64", r.Float64(), r.Float64(), r.Float64()) + + // ExpFloat64 values have an average of 1 but decay exponentially. + show("ExpFloat64", r.ExpFloat64(), r.ExpFloat64(), r.ExpFloat64()) + + // NormFloat64 values have an average of 0 and a standard deviation of 1. + show("NormFloat64", r.NormFloat64(), r.NormFloat64(), r.NormFloat64()) + + // Int32, Int64, and Uint32 generate values of the given width. + // The Int method (not shown) is like either Int32 or Int64 + // depending on the size of 'int'. + show("Int32", r.Int32(), r.Int32(), r.Int32()) + show("Int64", r.Int64(), r.Int64(), r.Int64()) + show("Uint32", r.Uint32(), r.Uint32(), r.Uint32()) + + // IntN, Int32N, and Int64N limit their output to be < n. + // They do so more carefully than using r.Int()%n. + show("IntN(10)", r.IntN(10), r.IntN(10), r.IntN(10)) + show("Int32N(10)", r.Int32N(10), r.Int32N(10), r.Int32N(10)) + show("Int64N(10)", r.Int64N(10), r.Int64N(10), r.Int64N(10)) + + // Perm generates a random permutation of the numbers [0, n). + show("Perm", r.Perm(5), r.Perm(5), r.Perm(5)) + // Output: + // Float32 0.95955694 0.8076733 0.8135684 + // Float64 0.4297927436037299 0.797802349388613 0.3883664855410056 + // ExpFloat64 0.43463410545541104 0.5513632046504593 0.7426404617374481 + // NormFloat64 -0.9303318111676635 -0.04750789419852852 0.22248301107582735 + // Int32 2020777787 260808523 851126509 + // Int64 5231057920893523323 4257872588489500903 158397175702351138 + // Uint32 314478343 1418758728 208955345 + // IntN(10) 6 2 0 + // Int32N(10) 3 7 7 + // Int64N(10) 8 9 4 + // Perm [0 3 1 4 2] [4 1 2 0 3] [4 3 2 0 1] +} + +func ExamplePerm() { + for _, value := range rand.Perm(3) { + fmt.Println(value) + } + + // Unordered output: 1 + // 2 + // 0 +} + +func ExampleShuffle() { + words := strings.Fields("ink runs from the corners of my mouth") + rand.Shuffle(len(words), func(i, j int) { + words[i], words[j] = words[j], words[i] + }) + fmt.Println(words) +} + +func ExampleShuffle_slicesInUnison() { + numbers := []byte("12345") + letters := []byte("ABCDE") + // Shuffle numbers, swapping corresponding entries in letters at the same time. + rand.Shuffle(len(numbers), func(i, j int) { + numbers[i], numbers[j] = numbers[j], numbers[i] + letters[i], letters[j] = letters[j], letters[i] + }) + for i := range numbers { + fmt.Printf("%c: %c\n", letters[i], numbers[i]) + } +} + +func ExampleIntN() { + fmt.Println(rand.IntN(100)) + fmt.Println(rand.IntN(100)) + fmt.Println(rand.IntN(100)) +} diff --git a/gnovm/stdlibs/math/rand/exp.gno b/gnovm/stdlibs/math/rand/exp.gno new file mode 100644 index 00000000000..d78874b735f --- /dev/null +++ b/gnovm/stdlibs/math/rand/exp.gno @@ -0,0 +1,224 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "math" +) + +/* + * Exponential distribution + * + * See "The Ziggurat Method for Generating Random Variables" + * (Marsaglia & Tsang, 2000) + * https://www.jstatsoft.org/v05/i08/paper [pdf] + */ + +const ( + re = 7.69711747013104972 +) + +// ExpFloat64 returns an exponentially distributed float64 in the range +// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter +// (lambda) is 1 and whose mean is 1/lambda (1). +// To produce a distribution with a different rate parameter, +// callers can adjust the output using: +// +// sample = ExpFloat64() / desiredRateParameter +func (r *Rand) ExpFloat64() float64 { + for { + u := r.Uint64() + j := uint32(u) + i := uint8(u >> 32) + x := float64(j) * float64(we[i]) + if j < ke[i] { + return x + } + if i == 0 { + return re - math.Log(r.Float64()) + } + if fe[i]+float32(r.Float64())*(fe[i-1]-fe[i]) < float32(math.Exp(-x)) { + return x + } + } +} + +var ke = [256]uint32{ + 0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990, + 0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8, + 0xf0204efd, 0xf19bdb8e, 0xf2d458bb, 0xf3da104b, 0xf4b86d78, + 0xf577ad8a, 0xf61de83d, 0xf6afb784, 0xf730a573, 0xf7a37651, + 0xf80a5bb6, 0xf867189d, 0xf8bb1b4f, 0xf9079062, 0xf94d70ca, + 0xf98d8c7d, 0xf9c8928a, 0xf9ff175b, 0xfa319996, 0xfa6085f8, + 0xfa8c3a62, 0xfab5084e, 0xfadb36c8, 0xfaff0410, 0xfb20a6ea, + 0xfb404fb4, 0xfb5e2951, 0xfb7a59e9, 0xfb95038c, 0xfbae44ba, + 0xfbc638d8, 0xfbdcf892, 0xfbf29a30, 0xfc0731df, 0xfc1ad1ed, + 0xfc2d8b02, 0xfc3f6c4d, 0xfc5083ac, 0xfc60ddd1, 0xfc708662, + 0xfc7f8810, 0xfc8decb4, 0xfc9bbd62, 0xfca9027c, 0xfcb5c3c3, + 0xfcc20864, 0xfccdd70a, 0xfcd935e3, 0xfce42ab0, 0xfceebace, + 0xfcf8eb3b, 0xfd02c0a0, 0xfd0c3f59, 0xfd156b7b, 0xfd1e48d6, + 0xfd26daff, 0xfd2f2552, 0xfd372af7, 0xfd3eeee5, 0xfd4673e7, + 0xfd4dbc9e, 0xfd54cb85, 0xfd5ba2f2, 0xfd62451b, 0xfd68b415, + 0xfd6ef1da, 0xfd750047, 0xfd7ae120, 0xfd809612, 0xfd8620b4, + 0xfd8b8285, 0xfd90bcf5, 0xfd95d15e, 0xfd9ac10b, 0xfd9f8d36, + 0xfda43708, 0xfda8bf9e, 0xfdad2806, 0xfdb17141, 0xfdb59c46, + 0xfdb9a9fd, 0xfdbd9b46, 0xfdc170f6, 0xfdc52bd8, 0xfdc8ccac, + 0xfdcc542d, 0xfdcfc30b, 0xfdd319ef, 0xfdd6597a, 0xfdd98245, + 0xfddc94e5, 0xfddf91e6, 0xfde279ce, 0xfde54d1f, 0xfde80c52, + 0xfdeab7de, 0xfded5034, 0xfdefd5be, 0xfdf248e3, 0xfdf4aa06, + 0xfdf6f984, 0xfdf937b6, 0xfdfb64f4, 0xfdfd818d, 0xfdff8dd0, + 0xfe018a08, 0xfe03767a, 0xfe05536c, 0xfe07211c, 0xfe08dfc9, + 0xfe0a8fab, 0xfe0c30fb, 0xfe0dc3ec, 0xfe0f48b1, 0xfe10bf76, + 0xfe122869, 0xfe1383b4, 0xfe14d17c, 0xfe1611e7, 0xfe174516, + 0xfe186b2a, 0xfe19843e, 0xfe1a9070, 0xfe1b8fd6, 0xfe1c8289, + 0xfe1d689b, 0xfe1e4220, 0xfe1f0f26, 0xfe1fcfbc, 0xfe2083ed, + 0xfe212bc3, 0xfe21c745, 0xfe225678, 0xfe22d95f, 0xfe234ffb, + 0xfe23ba4a, 0xfe241849, 0xfe2469f2, 0xfe24af3c, 0xfe24e81e, + 0xfe25148b, 0xfe253474, 0xfe2547c7, 0xfe254e70, 0xfe25485a, + 0xfe25356a, 0xfe251586, 0xfe24e88f, 0xfe24ae64, 0xfe2466e1, + 0xfe2411df, 0xfe23af34, 0xfe233eb4, 0xfe22c02c, 0xfe22336b, + 0xfe219838, 0xfe20ee58, 0xfe20358c, 0xfe1f6d92, 0xfe1e9621, + 0xfe1daef0, 0xfe1cb7ac, 0xfe1bb002, 0xfe1a9798, 0xfe196e0d, + 0xfe1832fd, 0xfe16e5fe, 0xfe15869d, 0xfe141464, 0xfe128ed3, + 0xfe10f565, 0xfe0f478c, 0xfe0d84b1, 0xfe0bac36, 0xfe09bd73, + 0xfe07b7b5, 0xfe059a40, 0xfe03644c, 0xfe011504, 0xfdfeab88, + 0xfdfc26e9, 0xfdf98629, 0xfdf6c83b, 0xfdf3ec01, 0xfdf0f04a, + 0xfdedd3d1, 0xfdea953d, 0xfde7331e, 0xfde3abe9, 0xfddffdfb, + 0xfddc2791, 0xfdd826cd, 0xfdd3f9a8, 0xfdcf9dfc, 0xfdcb1176, + 0xfdc65198, 0xfdc15bb3, 0xfdbc2ce2, 0xfdb6c206, 0xfdb117be, + 0xfdab2a63, 0xfda4f5fd, 0xfd9e7640, 0xfd97a67a, 0xfd908192, + 0xfd8901f2, 0xfd812182, 0xfd78d98e, 0xfd7022bb, 0xfd66f4ed, + 0xfd5d4732, 0xfd530f9c, 0xfd48432b, 0xfd3cd59a, 0xfd30b936, + 0xfd23dea4, 0xfd16349e, 0xfd07a7a3, 0xfcf8219b, 0xfce7895b, + 0xfcd5c220, 0xfcc2aadb, 0xfcae1d5e, 0xfc97ed4e, 0xfc7fe6d4, + 0xfc65ccf3, 0xfc495762, 0xfc2a2fc8, 0xfc07ee19, 0xfbe213c1, + 0xfbb8051a, 0xfb890078, 0xfb5411a5, 0xfb180005, 0xfad33482, + 0xfa839276, 0xfa263b32, 0xf9b72d1c, 0xf930a1a2, 0xf889f023, + 0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d, + 0xe6da6ecf, +} + +var we = [256]float32{ + 2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11, + 3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11, + 5.905944e-11, 6.344942e-11, 6.7643814e-11, 7.1672945e-11, + 7.556032e-11, 7.932458e-11, 8.298079e-11, 8.654132e-11, + 9.0016515e-11, 9.3415074e-11, 9.674443e-11, 1.0001099e-10, + 1.03220314e-10, 1.06377254e-10, 1.09486115e-10, 1.1255068e-10, + 1.1557435e-10, 1.1856015e-10, 1.2151083e-10, 1.2442886e-10, + 1.2731648e-10, 1.3017575e-10, 1.3300853e-10, 1.3581657e-10, + 1.3860142e-10, 1.4136457e-10, 1.4410738e-10, 1.4683108e-10, + 1.4953687e-10, 1.5222583e-10, 1.54899e-10, 1.5755733e-10, + 1.6020171e-10, 1.6283301e-10, 1.6545203e-10, 1.6805951e-10, + 1.7065617e-10, 1.732427e-10, 1.7581973e-10, 1.7838787e-10, + 1.8094774e-10, 1.8349985e-10, 1.8604476e-10, 1.8858298e-10, + 1.9111498e-10, 1.9364126e-10, 1.9616223e-10, 1.9867835e-10, + 2.0119004e-10, 2.0369768e-10, 2.0620168e-10, 2.087024e-10, + 2.1120022e-10, 2.136955e-10, 2.1618855e-10, 2.1867974e-10, + 2.2116936e-10, 2.2365775e-10, 2.261452e-10, 2.2863202e-10, + 2.311185e-10, 2.3360494e-10, 2.360916e-10, 2.3857874e-10, + 2.4106667e-10, 2.4355562e-10, 2.4604588e-10, 2.485377e-10, + 2.5103128e-10, 2.5352695e-10, 2.560249e-10, 2.585254e-10, + 2.6102867e-10, 2.6353494e-10, 2.6604446e-10, 2.6855745e-10, + 2.7107416e-10, 2.7359479e-10, 2.761196e-10, 2.7864877e-10, + 2.8118255e-10, 2.8372119e-10, 2.8626485e-10, 2.888138e-10, + 2.9136826e-10, 2.939284e-10, 2.9649452e-10, 2.9906677e-10, + 3.016454e-10, 3.0423064e-10, 3.0682268e-10, 3.0942177e-10, + 3.1202813e-10, 3.1464195e-10, 3.1726352e-10, 3.19893e-10, + 3.2253064e-10, 3.251767e-10, 3.2783135e-10, 3.3049485e-10, + 3.3316744e-10, 3.3584938e-10, 3.3854083e-10, 3.4124212e-10, + 3.4395342e-10, 3.46675e-10, 3.4940711e-10, 3.5215003e-10, + 3.5490397e-10, 3.5766917e-10, 3.6044595e-10, 3.6323455e-10, + 3.660352e-10, 3.6884823e-10, 3.7167386e-10, 3.745124e-10, + 3.773641e-10, 3.802293e-10, 3.8310827e-10, 3.860013e-10, + 3.8890866e-10, 3.918307e-10, 3.9476775e-10, 3.9772008e-10, + 4.0068804e-10, 4.0367196e-10, 4.0667217e-10, 4.09689e-10, + 4.1272286e-10, 4.1577405e-10, 4.1884296e-10, 4.2192994e-10, + 4.250354e-10, 4.281597e-10, 4.313033e-10, 4.3446652e-10, + 4.3764986e-10, 4.408537e-10, 4.4407847e-10, 4.4732465e-10, + 4.5059267e-10, 4.5388301e-10, 4.571962e-10, 4.6053267e-10, + 4.6389292e-10, 4.6727755e-10, 4.70687e-10, 4.741219e-10, + 4.7758275e-10, 4.810702e-10, 4.845848e-10, 4.8812715e-10, + 4.9169796e-10, 4.9529775e-10, 4.989273e-10, 5.0258725e-10, + 5.0627835e-10, 5.100013e-10, 5.1375687e-10, 5.1754584e-10, + 5.21369e-10, 5.2522725e-10, 5.2912136e-10, 5.330522e-10, + 5.370208e-10, 5.4102806e-10, 5.45075e-10, 5.491625e-10, + 5.532918e-10, 5.5746385e-10, 5.616799e-10, 5.6594107e-10, + 5.7024857e-10, 5.746037e-10, 5.7900773e-10, 5.834621e-10, + 5.8796823e-10, 5.925276e-10, 5.971417e-10, 6.018122e-10, + 6.065408e-10, 6.113292e-10, 6.1617933e-10, 6.2109295e-10, + 6.260722e-10, 6.3111916e-10, 6.3623595e-10, 6.4142497e-10, + 6.4668854e-10, 6.5202926e-10, 6.5744976e-10, 6.6295286e-10, + 6.6854156e-10, 6.742188e-10, 6.79988e-10, 6.858526e-10, + 6.9181616e-10, 6.978826e-10, 7.04056e-10, 7.103407e-10, + 7.167412e-10, 7.2326256e-10, 7.2990985e-10, 7.366886e-10, + 7.4360473e-10, 7.5066453e-10, 7.5787476e-10, 7.6524265e-10, + 7.7277595e-10, 7.80483e-10, 7.883728e-10, 7.9645507e-10, + 8.047402e-10, 8.1323964e-10, 8.219657e-10, 8.309319e-10, + 8.401528e-10, 8.496445e-10, 8.594247e-10, 8.6951274e-10, + 8.799301e-10, 8.9070046e-10, 9.018503e-10, 9.134092e-10, + 9.254101e-10, 9.378904e-10, 9.508923e-10, 9.644638e-10, + 9.786603e-10, 9.935448e-10, 1.0091913e-09, 1.025686e-09, + 1.0431306e-09, 1.0616465e-09, 1.08138e-09, 1.1025096e-09, + 1.1252564e-09, 1.1498986e-09, 1.1767932e-09, 1.206409e-09, + 1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09, + 1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09, +} + +var fe = [256]float32{ + 1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933, + 0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686, + 0.7350381, 0.72286767, 0.71127474, 0.70019263, 0.6895665, + 0.67935055, 0.6695063, 0.66000086, 0.65080583, 0.6418967, + 0.63325197, 0.6248527, 0.6166822, 0.60872537, 0.60096896, + 0.5934009, 0.58601034, 0.5787874, 0.57172304, 0.5648092, + 0.5580383, 0.5514034, 0.5448982, 0.5385169, 0.53225386, + 0.5261042, 0.52006316, 0.5141264, 0.50828975, 0.5025495, + 0.496902, 0.49134386, 0.485872, 0.48048335, 0.4751752, + 0.46994483, 0.46478975, 0.45970762, 0.45469615, 0.44975325, + 0.44487688, 0.44006512, 0.43531612, 0.43062815, 0.42599955, + 0.42142874, 0.4169142, 0.41245446, 0.40804818, 0.403694, + 0.3993907, 0.39513698, 0.39093173, 0.38677382, 0.38266218, + 0.37859577, 0.37457356, 0.37059465, 0.3666581, 0.362763, + 0.35890847, 0.35509375, 0.351318, 0.3475805, 0.34388044, + 0.34021714, 0.3365899, 0.33299807, 0.32944095, 0.32591796, + 0.3224285, 0.3189719, 0.31554767, 0.31215525, 0.30879408, + 0.3054636, 0.3021634, 0.29889292, 0.2956517, 0.29243928, + 0.28925523, 0.28609908, 0.28297043, 0.27986884, 0.27679393, + 0.2737453, 0.2707226, 0.2677254, 0.26475343, 0.26180625, + 0.25888354, 0.25598502, 0.2531103, 0.25025907, 0.24743107, + 0.24462597, 0.24184346, 0.23908329, 0.23634516, 0.23362878, + 0.23093392, 0.2282603, 0.22560766, 0.22297576, 0.22036438, + 0.21777324, 0.21520215, 0.21265087, 0.21011916, 0.20760682, + 0.20511365, 0.20263945, 0.20018397, 0.19774707, 0.19532852, + 0.19292815, 0.19054577, 0.1881812, 0.18583426, 0.18350479, + 0.1811926, 0.17889754, 0.17661946, 0.17435817, 0.17211354, + 0.1698854, 0.16767362, 0.16547804, 0.16329853, 0.16113494, + 0.15898713, 0.15685499, 0.15473837, 0.15263714, 0.15055119, + 0.14848037, 0.14642459, 0.14438373, 0.14235765, 0.14034624, + 0.13834943, 0.13636707, 0.13439907, 0.13244532, 0.13050574, + 0.1285802, 0.12666863, 0.12477092, 0.12288698, 0.12101672, + 0.119160056, 0.1173169, 0.115487166, 0.11367077, 0.11186763, + 0.11007768, 0.10830083, 0.10653701, 0.10478614, 0.10304816, + 0.101323, 0.09961058, 0.09791085, 0.09622374, 0.09454919, + 0.09288713, 0.091237515, 0.08960028, 0.087975375, 0.08636274, + 0.08476233, 0.083174095, 0.081597984, 0.08003395, 0.07848195, + 0.076941945, 0.07541389, 0.07389775, 0.072393484, 0.07090106, + 0.069420435, 0.06795159, 0.066494495, 0.06504912, 0.063615434, + 0.062193416, 0.060783047, 0.059384305, 0.057997175, + 0.05662164, 0.05525769, 0.053905312, 0.052564494, 0.051235236, + 0.049917534, 0.048611384, 0.047316793, 0.046033762, 0.0447623, + 0.043502413, 0.042254124, 0.041017443, 0.039792392, + 0.038578995, 0.037377283, 0.036187284, 0.035009038, + 0.033842582, 0.032687962, 0.031545233, 0.030414443, 0.02929566, + 0.02818895, 0.027094385, 0.026012046, 0.024942026, 0.023884421, + 0.022839336, 0.021806888, 0.020787204, 0.019780423, 0.0187867, + 0.0178062, 0.016839107, 0.015885621, 0.014945968, 0.014020392, + 0.013109165, 0.012212592, 0.011331013, 0.01046481, 0.009614414, + 0.008780315, 0.007963077, 0.0071633533, 0.006381906, + 0.0056196423, 0.0048776558, 0.004157295, 0.0034602648, + 0.0027887989, 0.0021459677, 0.0015362998, 0.0009672693, + 0.00045413437, +} diff --git a/gnovm/stdlibs/math/rand/normal.gno b/gnovm/stdlibs/math/rand/normal.gno new file mode 100644 index 00000000000..9771c835317 --- /dev/null +++ b/gnovm/stdlibs/math/rand/normal.gno @@ -0,0 +1,159 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "math" +) + +/* + * Normal distribution + * + * See "The Ziggurat Method for Generating Random Variables" + * (Marsaglia & Tsang, 2000) + * http://www.jstatsoft.org/v05/i08/paper [pdf] + */ + +const ( + rn = 3.442619855899 +) + +func absInt32(i int32) uint32 { + if i < 0 { + return uint32(-i) + } + return uint32(i) +} + +// NormFloat64 returns a normally distributed float64 in +// the range -math.MaxFloat64 through +math.MaxFloat64 inclusive, +// with standard normal distribution (mean = 0, stddev = 1). +// To produce a different normal distribution, callers can +// adjust the output using: +// +// sample = NormFloat64() * desiredStdDev + desiredMean +func (r *Rand) NormFloat64() float64 { + for { + u := r.Uint64() + j := int32(u) // Possibly negative + i := u >> 32 & 0x7F + x := float64(j) * float64(wn[i]) + if absInt32(j) < kn[i] { + // This case should be hit better than 99% of the time. + return x + } + + if i == 0 { + // This extra work is only required for the base strip. + for { + x = -math.Log(r.Float64()) * (1.0 / rn) + y := -math.Log(r.Float64()) + if y+y >= x*x { + break + } + } + if j > 0 { + return rn + x + } + return -rn - x + } + if fn[i]+float32(r.Float64())*(fn[i-1]-fn[i]) < float32(math.Exp(-.5*x*x)) { + return x + } + } +} + +var kn = [128]uint32{ + 0x76ad2212, 0x0, 0x600f1b53, 0x6ce447a6, 0x725b46a2, + 0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d, + 0x7adf629f, 0x7b5682a6, 0x7bb8a8c6, 0x7c0ae722, 0x7c50cce7, + 0x7c8cec5b, 0x7cc12cd6, 0x7ceefed2, 0x7d177e0b, 0x7d3b8883, + 0x7d5bce6c, 0x7d78dd64, 0x7d932886, 0x7dab0e57, 0x7dc0dd30, + 0x7dd4d688, 0x7de73185, 0x7df81cea, 0x7e07c0a3, 0x7e163efa, + 0x7e23b587, 0x7e303dfd, 0x7e3beec2, 0x7e46db77, 0x7e51155d, + 0x7e5aabb3, 0x7e63abf7, 0x7e6c222c, 0x7e741906, 0x7e7b9a18, + 0x7e82adfa, 0x7e895c63, 0x7e8fac4b, 0x7e95a3fb, 0x7e9b4924, + 0x7ea0a0ef, 0x7ea5b00d, 0x7eaa7ac3, 0x7eaf04f3, 0x7eb3522a, + 0x7eb765a5, 0x7ebb4259, 0x7ebeeafd, 0x7ec2620a, 0x7ec5a9c4, + 0x7ec8c441, 0x7ecbb365, 0x7ece78ed, 0x7ed11671, 0x7ed38d62, + 0x7ed5df12, 0x7ed80cb4, 0x7eda175c, 0x7edc0005, 0x7eddc78e, + 0x7edf6ebf, 0x7ee0f647, 0x7ee25ebe, 0x7ee3a8a9, 0x7ee4d473, + 0x7ee5e276, 0x7ee6d2f5, 0x7ee7a620, 0x7ee85c10, 0x7ee8f4cd, + 0x7ee97047, 0x7ee9ce59, 0x7eea0eca, 0x7eea3147, 0x7eea3568, + 0x7eea1aab, 0x7ee9e071, 0x7ee98602, 0x7ee90a88, 0x7ee86d08, + 0x7ee7ac6a, 0x7ee6c769, 0x7ee5bc9c, 0x7ee48a67, 0x7ee32efc, + 0x7ee1a857, 0x7edff42f, 0x7ede0ffa, 0x7edbf8d9, 0x7ed9ab94, + 0x7ed7248d, 0x7ed45fae, 0x7ed1585c, 0x7ece095f, 0x7eca6ccb, + 0x7ec67be2, 0x7ec22eee, 0x7ebd7d1a, 0x7eb85c35, 0x7eb2c075, + 0x7eac9c20, 0x7ea5df27, 0x7e9e769f, 0x7e964c16, 0x7e8d44ba, + 0x7e834033, 0x7e781728, 0x7e6b9933, 0x7e5d8a1a, 0x7e4d9ded, + 0x7e3b737a, 0x7e268c2f, 0x7e0e3ff5, 0x7df1aa5d, 0x7dcf8c72, + 0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a, + 0x7ba90bdc, 0x7a722176, 0x77d664e5, +} + +var wn = [128]float32{ + 1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10, + 2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10, + 2.9073963e-10, 3.042997e-10, 3.1699796e-10, 3.289802e-10, + 3.4035738e-10, 3.5121603e-10, 3.616251e-10, 3.7164058e-10, + 3.8130857e-10, 3.9066758e-10, 3.9975012e-10, 4.08584e-10, + 4.1719309e-10, 4.2559822e-10, 4.338176e-10, 4.418672e-10, + 4.497613e-10, 4.5751258e-10, 4.651324e-10, 4.7263105e-10, + 4.8001775e-10, 4.87301e-10, 4.944885e-10, 5.015873e-10, + 5.0860405e-10, 5.155446e-10, 5.2241467e-10, 5.2921934e-10, + 5.359635e-10, 5.426517e-10, 5.4928817e-10, 5.5587696e-10, + 5.624219e-10, 5.6892646e-10, 5.753941e-10, 5.818282e-10, + 5.882317e-10, 5.946077e-10, 6.00959e-10, 6.072884e-10, + 6.135985e-10, 6.19892e-10, 6.2617134e-10, 6.3243905e-10, + 6.386974e-10, 6.449488e-10, 6.511956e-10, 6.5744005e-10, + 6.6368433e-10, 6.699307e-10, 6.7618144e-10, 6.824387e-10, + 6.8870465e-10, 6.949815e-10, 7.012715e-10, 7.075768e-10, + 7.1389966e-10, 7.202424e-10, 7.266073e-10, 7.329966e-10, + 7.394128e-10, 7.4585826e-10, 7.5233547e-10, 7.58847e-10, + 7.653954e-10, 7.719835e-10, 7.7861395e-10, 7.852897e-10, + 7.920138e-10, 7.987892e-10, 8.0561924e-10, 8.125073e-10, + 8.194569e-10, 8.2647167e-10, 8.3355556e-10, 8.407127e-10, + 8.479473e-10, 8.55264e-10, 8.6266755e-10, 8.7016316e-10, + 8.777562e-10, 8.8545243e-10, 8.932582e-10, 9.0117996e-10, + 9.09225e-10, 9.174008e-10, 9.2571584e-10, 9.341788e-10, + 9.427997e-10, 9.515889e-10, 9.605579e-10, 9.697193e-10, + 9.790869e-10, 9.88676e-10, 9.985036e-10, 1.0085882e-09, + 1.0189509e-09, 1.0296151e-09, 1.0406069e-09, 1.0519566e-09, + 1.063698e-09, 1.0758702e-09, 1.0885183e-09, 1.1016947e-09, + 1.1154611e-09, 1.1298902e-09, 1.1450696e-09, 1.1611052e-09, + 1.1781276e-09, 1.1962995e-09, 1.2158287e-09, 1.2369856e-09, + 1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09, + 1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09, +} + +var fn = [128]float32{ + 1, 0.9635997, 0.9362827, 0.9130436, 0.89228165, 0.87324303, + 0.8555006, 0.8387836, 0.8229072, 0.8077383, 0.793177, + 0.7791461, 0.7655842, 0.7524416, 0.73967725, 0.7272569, + 0.7151515, 0.7033361, 0.69178915, 0.68049186, 0.6694277, + 0.658582, 0.6479418, 0.63749546, 0.6272325, 0.6171434, + 0.6072195, 0.5974532, 0.58783704, 0.5783647, 0.56903, + 0.5598274, 0.5507518, 0.54179835, 0.5329627, 0.52424055, + 0.5156282, 0.50712204, 0.49871865, 0.49041483, 0.48220766, + 0.4740943, 0.46607214, 0.4581387, 0.45029163, 0.44252872, + 0.43484783, 0.427247, 0.41972435, 0.41227803, 0.40490642, + 0.39760786, 0.3903808, 0.3832238, 0.37613547, 0.36911446, + 0.3621595, 0.35526937, 0.34844297, 0.34167916, 0.33497685, + 0.3283351, 0.3217529, 0.3152294, 0.30876362, 0.30235484, + 0.29600215, 0.28970486, 0.2834622, 0.2772735, 0.27113807, + 0.2650553, 0.25902456, 0.2530453, 0.24711695, 0.241239, + 0.23541094, 0.22963232, 0.2239027, 0.21822165, 0.21258877, + 0.20700371, 0.20146611, 0.19597565, 0.19053204, 0.18513499, + 0.17978427, 0.17447963, 0.1692209, 0.16400786, 0.15884037, + 0.15371831, 0.14864157, 0.14361008, 0.13862377, 0.13368265, + 0.12878671, 0.12393598, 0.119130544, 0.11437051, 0.10965602, + 0.104987256, 0.10036444, 0.095787846, 0.0912578, 0.08677467, + 0.0823389, 0.077950984, 0.073611505, 0.06932112, 0.06508058, + 0.06089077, 0.056752663, 0.0526674, 0.048636295, 0.044660863, + 0.040742867, 0.03688439, 0.033087887, 0.029356318, + 0.025693292, 0.022103304, 0.018592102, 0.015167298, + 0.011839478, 0.008624485, 0.005548995, 0.0026696292, +} diff --git a/gnovm/stdlibs/math/rand/pcg.gno b/gnovm/stdlibs/math/rand/pcg.gno new file mode 100644 index 00000000000..77708d799e2 --- /dev/null +++ b/gnovm/stdlibs/math/rand/pcg.gno @@ -0,0 +1,121 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "errors" + "math/bits" +) + +// https://numpy.org/devdocs/reference/random/upgrading-pcg64.html +// https://github.com/imneme/pcg-cpp/commit/871d0494ee9c9a7b7c43f753e3d8ca47c26f8005 + +// A PCG is a PCG generator with 128 bits of internal state. +// A zero PCG is equivalent to NewPCG(0, 0). +type PCG struct { + hi uint64 + lo uint64 +} + +// NewPCG returns a new PCG seeded with the given values. +func NewPCG(seed1, seed2 uint64) *PCG { + return &PCG{seed1, seed2} +} + +// Seed resets the PCG to behave the same way as NewPCG(seed1, seed2). +func (p *PCG) Seed(seed1, seed2 uint64) { + p.hi = seed1 + p.lo = seed2 +} + +// binary.bigEndian.Uint64, copied to avoid dependency +func beUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +// binary.bigEndian.PutUint64, copied to avoid dependency +func bePutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (p *PCG) MarshalBinary() ([]byte, error) { + b := make([]byte, 20) + copy(b, "pcg:") + bePutUint64(b[4:], p.hi) + bePutUint64(b[4+8:], p.lo) + return b, nil +} + +var errUnmarshalPCG = errors.New("invalid PCG encoding") + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (p *PCG) UnmarshalBinary(data []byte) error { + if len(data) != 20 || string(data[:4]) != "pcg:" { + return errUnmarshalPCG + } + p.hi = beUint64(data[4:]) + p.lo = beUint64(data[4+8:]) + return nil +} + +func (p *PCG) next() (hi, lo uint64) { + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L161 + // + // Numpy's PCG multiplies by the 64-bit value cheapMul + // instead of the 128-bit value used here and in the official PCG code. + // This does not seem worthwhile, at least for Go: not having any high + // bits in the multiplier reduces the effect of low bits on the highest bits, + // and it only saves 1 multiply out of 3. + // (On 32-bit systems, it saves 1 out of 6, since Mul64 is doing 4.) + const ( + mulHi = 2549297995355413924 + mulLo = 4865540595714422341 + incHi = 6364136223846793005 + incLo = 1442695040888963407 + ) + + // state = state * mul + inc + hi, lo = bits.Mul64(p.lo, mulLo) + hi += p.hi*mulLo + p.lo*mulHi + lo, c := bits.Add64(lo, incLo, 0) + hi, _ = bits.Add64(hi, incHi, c) + p.lo = lo + p.hi = hi + return hi, lo +} + +// Uint64 return a uniformly-distributed random uint64 value. +func (p *PCG) Uint64() uint64 { + hi, lo := p.next() + + // XSL-RR would be + // hi, lo := p.next() + // return bits.RotateLeft64(lo^hi, -int(hi>>58)) + // but Numpy uses DXSM and O'Neill suggests doing the same. + // See https://github.com/golang/go/issues/21835#issuecomment-739065688 + // and following comments. + + // DXSM "double xorshift multiply" + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L1015 + + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L176 + const cheapMul = 0xda942042e4dd58b5 + hi ^= hi >> 32 + hi *= cheapMul + hi ^= hi >> 48 + hi *= (lo | 1) + return hi +} diff --git a/gnovm/stdlibs/math/rand/pcg_test.gno b/gnovm/stdlibs/math/rand/pcg_test.gno new file mode 100644 index 00000000000..843506d7234 --- /dev/null +++ b/gnovm/stdlibs/math/rand/pcg_test.gno @@ -0,0 +1,78 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "testing" +) + +func BenchmarkPCG_DXSM(b *testing.B) { + var p PCG + var t uint64 + for n := b.N; n > 0; n-- { + t += p.Uint64() + } + Sink = t +} + +func TestPCGMarshal(t *testing.T) { + var p PCG + const ( + seed1 = 0x123456789abcdef0 + seed2 = 0xfedcba9876543210 + want = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10" + ) + p.Seed(seed1, seed2) + data, err := p.MarshalBinary() + if string(data) != want || err != nil { + t.Errorf("MarshalBinary() = %q, %v, want %q, nil", data, err, want) + } + + q := PCG{} + if err := q.UnmarshalBinary([]byte(want)); err != nil { + t.Fatalf("UnmarshalBinary(): %v", err) + } + if q != p { + t.Fatalf("after round trip, q = %#x, but p = %#x", q, p) + } + + qu := q.Uint64() + pu := p.Uint64() + if qu != pu { + t.Errorf("after round trip, q.Uint64() = %#x, but p.Uint64() = %#x", qu, pu) + } +} + +func TestPCG(t *testing.T) { + p := NewPCG(1, 2) + want := []uint64{ + 0xc4f5a58656eef510, + 0x9dcec3ad077dec6c, + 0xc8d04605312f8088, + 0xcbedc0dcb63ac19a, + 0x3bf98798cae97950, + 0xa8c6d7f8d485abc, + 0x7ffa3780429cd279, + 0x730ad2626b1c2f8e, + 0x21ff2330f4a0ad99, + 0x2f0901a1947094b0, + 0xa9735a3cfbe36cef, + 0x71ddb0a01a12c84a, + 0xf0e53e77a78453bb, + 0x1f173e9663be1e9d, + 0x657651da3ac4115e, + 0xc8987376b65a157b, + 0xbb17008f5fca28e7, + 0x8232bd645f29ed22, + 0x12be8f07ad14c539, + 0x54908a48e8e4736e, + } + + for i, x := range want { + if u := p.Uint64(); u != x { + t.Errorf("PCG #%d = %#x, want %#x", i, u, x) + } + } +} diff --git a/gnovm/stdlibs/math/rand/rand.gno b/gnovm/stdlibs/math/rand/rand.gno new file mode 100644 index 00000000000..250ea7c5a05 --- /dev/null +++ b/gnovm/stdlibs/math/rand/rand.gno @@ -0,0 +1,340 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rand implements pseudo-random number generators suitable for tasks +// such as simulation, but it should not be used for security-sensitive work. +// +// IMPORTANT NOTE: This does not equate to Go's math/rand; instead, it uses +// the implementation in math/rand/v2, which improves on its algorithm and +// usability. +// +// Random numbers are generated by a [Source], usually wrapped in a [Rand]. +// Both types should be used by a single goroutine at a time: sharing among +// multiple goroutines requires some kind of synchronization. +// +// Top-level functions, such as [Float64] and [Int], +// are safe for concurrent use by multiple goroutines. +// +// This package's outputs might be easily predictable regardless of how it's +// seeded. +package rand + +import ( + "math/bits" +) + +// A Source is a source of uniformly-distributed +// pseudo-random uint64 values in the range [0, 1<<64). +// +// A Source is not safe for concurrent use by multiple goroutines. +type Source interface { + Uint64() uint64 +} + +// A Rand is a source of random numbers. +type Rand struct { + src Source +} + +// New returns a new Rand that uses random values from src +// to generate other random values. +func New(src Source) *Rand { + return &Rand{src: src} +} + +// Int64 returns a non-negative pseudo-random 63-bit integer as an int64. +func (r *Rand) Int64() int64 { return int64(r.src.Uint64() &^ (1 << 63)) } + +// Uint32 returns a pseudo-random 32-bit value as a uint32. +func (r *Rand) Uint32() uint32 { return uint32(r.src.Uint64() >> 32) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64. +func (r *Rand) Uint64() uint64 { return r.src.Uint64() } + +// Int32 returns a non-negative pseudo-random 31-bit integer as an int32. +func (r *Rand) Int32() int32 { return int32(r.src.Uint64() >> 33) } + +// Int returns a non-negative pseudo-random int. +func (r *Rand) Int() int { return int(uint(r.src.Uint64()) << 1 >> 1) } + +// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int64N(n int64) int64 { + if n <= 0 { + panic("invalid argument to Int64N") + } + return int64(r.uint64n(uint64(n))) +} + +// Uint64N returns, as a uint64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) Uint64N(n uint64) uint64 { + if n == 0 { + panic("invalid argument to Uint64N") + } + return r.uint64n(n) +} + +// uint64n is the no-bounds-checks version of Uint64N. +func (r *Rand) uint64n(n uint64) uint64 { + if is32bit && uint64(uint32(n)) == n { + return uint64(r.uint32n(uint32(n))) + } + if n&(n-1) == 0 { // n is power of two, can mask + return r.Uint64() & (n - 1) + } + + // Suppose we have a uint64 x uniform in the range [0,2⁶⁴) + // and want to reduce it to the range [0,n) preserving exact uniformity. + // We can simulate a scaling arbitrary precision x * (n/2⁶⁴) by + // the high bits of a double-width multiply of x*n, meaning (x*n)/2⁶⁴. + // Since there are 2⁶⁴ possible inputs x and only n possible outputs, + // the output is necessarily biased if n does not divide 2⁶⁴. + // In general (x*n)/2⁶⁴ = k for x*n in [k*2⁶⁴,(k+1)*2⁶⁴). + // There are either floor(2⁶⁴/n) or ceil(2⁶⁴/n) possible products + // in that range, depending on k. + // But suppose we reject the sample and try again when + // x*n is in [k*2⁶⁴, k*2⁶⁴+(2⁶⁴%n)), meaning rejecting fewer than n possible + // outcomes out of the 2⁶⁴. + // Now there are exactly floor(2⁶⁴/n) possible ways to produce + // each output value k, so we've restored uniformity. + // To get valid uint64 math, 2⁶⁴ % n = (2⁶⁴ - n) % n = -n % n, + // so the direct implementation of this algorithm would be: + // + // hi, lo := bits.Mul64(r.Uint64(), n) + // thresh := -n % n + // for lo < thresh { + // hi, lo = bits.Mul64(r.Uint64(), n) + // } + // + // That still leaves an expensive 64-bit division that we would rather avoid. + // We know that thresh < n, and n is usually much less than 2⁶⁴, so we can + // avoid the last four lines unless lo < n. + // + // See also: + // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction + // https://lemire.me/blog/2016/06/30/fast-random-shuffling + hi, lo := bits.Mul64(r.Uint64(), n) + if lo < n { + thresh := -n % n + for lo < thresh { + hi, lo = bits.Mul64(r.Uint64(), n) + } + } + return hi +} + +// uint32n is an identical computation to uint64n +// but optimized for 32-bit systems. +func (r *Rand) uint32n(n uint32) uint32 { + if n&(n-1) == 0 { // n is power of two, can mask + return uint32(r.Uint64()) & (n - 1) + } + // On 64-bit systems we still use the uint64 code below because + // the probability of a random uint64 lo being < a uint32 n is near zero, + // meaning the unbiasing loop almost never runs. + // On 32-bit systems, here we need to implement that same logic in 32-bit math, + // both to preserve the exact output sequence observed on 64-bit machines + // and to preserve the optimization that the unbiasing loop almost never runs. + // + // We want to compute + // hi, lo := bits.Mul64(r.Uint64(), n) + // In terms of 32-bit halves, this is: + // x1:x0 := r.Uint64() + // 0:hi, lo1:lo0 := bits.Mul64(x1:x0, 0:n) + // Writing out the multiplication in terms of bits.Mul32 allows + // using direct hardware instructions and avoiding + // the computations involving these zeros. + x := r.Uint64() + lo1a, lo0 := bits.Mul32(uint32(x), n) + hi, lo1b := bits.Mul32(uint32(x>>32), n) + lo1, c := bits.Add32(lo1a, lo1b, 0) + hi += c + if lo1 == 0 && lo0 < uint32(n) { + n64 := uint64(n) + thresh := uint32(-n64 % n64) + for lo1 == 0 && lo0 < thresh { + x := r.Uint64() + lo1a, lo0 = bits.Mul32(uint32(x), n) + hi, lo1b = bits.Mul32(uint32(x>>32), n) + lo1, c = bits.Add32(lo1a, lo1b, 0) + hi += c + } + } + return hi +} + +// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int32N(n int32) int32 { + if n <= 0 { + panic("invalid argument to Int32N") + } + return int32(r.uint64n(uint64(n))) +} + +// Uint32N returns, as a uint32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) Uint32N(n uint32) uint32 { + if n == 0 { + panic("invalid argument to Uint32N") + } + return uint32(r.uint64n(uint64(n))) +} + +const is32bit = ^uint(0)>>32 == 0 + +// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) IntN(n int) int { + if n <= 0 { + panic("invalid argument to IntN") + } + return int(r.uint64n(uint64(n))) +} + +// UintN returns, as a uint, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) UintN(n uint) uint { + if n == 0 { + panic("invalid argument to UintN") + } + return uint(r.uint64n(uint64(n))) +} + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float64() float64 { + // There are exactly 1<<53 float64s in [0,1). Use Intn(1<<53) / (1<<53). + return float64(r.Uint64()<<11>>11) / (1 << 53) +} + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float32() float32 { + // There are exactly 1<<24 float32s in [0,1). Use Intn(1<<24) / (1<<24). + return float32(r.Uint32()<<8>>8) / (1 << 24) +} + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n). +func (r *Rand) Perm(n int) []int { + p := make([]int, n) + for i := range p { + p[i] = i + } + r.Shuffle(len(p), func(i, j int) { p[i], p[j] = p[j], p[i] }) + return p +} + +// Shuffle pseudo-randomizes the order of elements. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func (r *Rand) Shuffle(n int, swap func(i, j int)) { + if n < 0 { + panic("invalid argument to Shuffle") + } + + // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + // Shuffle really ought not be called with n that doesn't fit in 32 bits. + // Not only will it take a very long time, but with 2³¹! possible permutations, + // there's no way that any PRNG can have a big enough internal state to + // generate even a minuscule percentage of the possible permutations. + // Nevertheless, the right API signature accepts an int n, so handle it as best we can. + for i := n - 1; i > 0; i-- { + j := int(r.uint64n(uint64(i + 1))) + swap(i, j) + } +} + +/* + * Top-level convenience functions + */ + +// globalRand is the source of random numbers for the top-level +// convenience functions. +var globalRand = &Rand{src: &PCG{}} + +// Int64 returns a non-negative pseudo-random 63-bit integer as an int64 +// from the default Source. +func Int64() int64 { return globalRand.Int64() } + +// Uint32 returns a pseudo-random 32-bit value as a uint32 +// from the default Source. +func Uint32() uint32 { return globalRand.Uint32() } + +// Uint64N returns, as a uint64, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Uint64N(n uint64) uint64 { return globalRand.Uint64N(n) } + +// Uint32N returns, as a uint32, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Uint32N(n uint32) uint32 { return globalRand.Uint32N(n) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64 +// from the default Source. +func Uint64() uint64 { return globalRand.Uint64() } + +// Int32 returns a non-negative pseudo-random 31-bit integer as an int32 +// from the default Source. +func Int32() int32 { return globalRand.Int32() } + +// Int returns a non-negative pseudo-random int from the default Source. +func Int() int { return globalRand.Int() } + +// Int64N returns, as an int64, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int64N(n int64) int64 { return globalRand.Int64N(n) } + +// Int32N returns, as an int32, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int32N(n int32) int32 { return globalRand.Int32N(n) } + +// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func IntN(n int) int { return globalRand.IntN(n) } + +// UintN returns, as a uint, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func UintN(n uint) uint { return globalRand.UintN(n) } + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float64() float64 { return globalRand.Float64() } + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float32() float32 { return globalRand.Float32() } + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n) from the default Source. +func Perm(n int) []int { return globalRand.Perm(n) } + +// Shuffle pseudo-randomizes the order of elements using the default Source. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) } + +// NormFloat64 returns a normally distributed float64 in the range +// [-math.MaxFloat64, +math.MaxFloat64] with +// standard normal distribution (mean = 0, stddev = 1) +// from the default Source. +// To produce a different normal distribution, callers can +// adjust the output using: +// +// sample = NormFloat64() * desiredStdDev + desiredMean +func NormFloat64() float64 { return globalRand.NormFloat64() } + +// ExpFloat64 returns an exponentially distributed float64 in the range +// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter +// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. +// To produce a distribution with a different rate parameter, +// callers can adjust the output using: +// +// sample = ExpFloat64() / desiredRateParameter +func ExpFloat64() float64 { return globalRand.ExpFloat64() } diff --git a/gnovm/stdlibs/math/rand/rand_test.gno b/gnovm/stdlibs/math/rand/rand_test.gno new file mode 100644 index 00000000000..0eff890dcc8 --- /dev/null +++ b/gnovm/stdlibs/math/rand/rand_test.gno @@ -0,0 +1,710 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "errors" + "fmt" + "math" + "testing" +) + +const ( + numTestSamples = 10000 +) + +type statsResults struct { + mean float64 + stddev float64 + closeEnough float64 + maxError float64 +} + +func max(a, b float64) float64 { + if a > b { + return a + } + return b +} + +func nearEqual(a, b, closeEnough, maxError float64) bool { + absDiff := math.Abs(a - b) + if absDiff < closeEnough { // Necessary when one value is zero and one value is close to zero. + return true + } + return absDiff/max(math.Abs(a), math.Abs(b)) < maxError +} + +var testSeeds = []uint64{1, 1754801282, 1698661970, 1550503961} + +// checkSimilarDistribution returns success if the mean and stddev of the +// two statsResults are similar. +func (this *statsResults) checkSimilarDistribution(expected *statsResults) error { + if !nearEqual(this.mean, expected.mean, expected.closeEnough, expected.maxError) { + s := fmt.Sprintf("mean %v != %v (allowed error %v, %v)", this.mean, expected.mean, expected.closeEnough, expected.maxError) + fmt.Println(s) + return errors.New(s) + } + if !nearEqual(this.stddev, expected.stddev, expected.closeEnough, expected.maxError) { + s := fmt.Sprintf("stddev %v != %v (allowed error %v, %v)", this.stddev, expected.stddev, expected.closeEnough, expected.maxError) + fmt.Println(s) + return errors.New(s) + } + return nil +} + +func getStatsResults(samples []float64) *statsResults { + res := new(statsResults) + var sum, squaresum float64 + for _, s := range samples { + sum += s + squaresum += s * s + } + res.mean = sum / float64(len(samples)) + res.stddev = math.Sqrt(squaresum/float64(len(samples)) - res.mean*res.mean) + return res +} + +func checkSampleDistribution(t *testing.T, samples []float64, expected *statsResults) { + t.Helper() + actual := getStatsResults(samples) + err := actual.checkSimilarDistribution(expected) + if err != nil { + t.Errorf(err.Error()) + } +} + +func checkSampleSliceDistributions(t *testing.T, samples []float64, nslices int, expected *statsResults) { + t.Helper() + chunk := len(samples) / nslices + for i := 0; i < nslices; i++ { + low := i * chunk + var high int + if i == nslices-1 { + high = len(samples) - 1 + } else { + high = (i + 1) * chunk + } + checkSampleDistribution(t, samples[low:high], expected) + } +} + +// +// Normal distribution tests +// + +func generateNormalSamples(nsamples int, mean, stddev float64, seed uint64) []float64 { + r := New(NewPCG(seed, seed)) + samples := make([]float64, nsamples) + for i := range samples { + samples[i] = r.NormFloat64()*stddev + mean + } + return samples +} + +func testNormalDistribution(t *testing.T, nsamples int, mean, stddev float64, seed uint64) { + // fmt.Printf("testing nsamples=%v mean=%v stddev=%v seed=%v\n", nsamples, mean, stddev, seed); + + samples := generateNormalSamples(nsamples, mean, stddev, seed) + errorScale := max(1.0, stddev) // Error scales with stddev + expected := &statsResults{mean, stddev, 0.10 * errorScale, 0.08 * errorScale} + + // Make sure that the entire set matches the expected distribution. + checkSampleDistribution(t, samples, expected) + + // Make sure that each half of the set matches the expected distribution. + checkSampleSliceDistributions(t, samples, 2, expected) + + // Make sure that each 7th of the set matches the expected distribution. + checkSampleSliceDistributions(t, samples, 7, expected) +} + +// Actual tests + +func TestStandardNormalValues(t *testing.T) { + for _, seed := range testSeeds { + testNormalDistribution(t, numTestSamples, 0, 1, seed) + } +} + +func TestNonStandardNormalValues(t *testing.T) { + sdmax := 1000.0 + mmax := 1000.0 + if testing.Short() { + sdmax = 5 + mmax = 5 + } + for sd := 0.5; sd < sdmax; sd *= 2 { + for m := 0.5; m < mmax; m *= 2 { + for _, seed := range testSeeds { + testNormalDistribution(t, numTestSamples, m, sd, seed) + if testing.Short() { + break + } + } + } + } +} + +// +// Exponential distribution tests +// + +func generateExponentialSamples(nsamples int, rate float64, seed uint64) []float64 { + r := New(NewPCG(seed, seed)) + samples := make([]float64, nsamples) + for i := range samples { + samples[i] = r.ExpFloat64() / rate + } + return samples +} + +func testExponentialDistribution(t *testing.T, nsamples int, rate float64, seed uint64) { + // fmt.Printf("testing nsamples=%v rate=%v seed=%v\n", nsamples, rate, seed); + + mean := 1 / rate + stddev := mean + + samples := generateExponentialSamples(nsamples, rate, seed) + errorScale := max(1.0, 1/rate) // Error scales with the inverse of the rate + expected := &statsResults{mean, stddev, 0.10 * errorScale, 0.20 * errorScale} + + // Make sure that the entire set matches the expected distribution. + checkSampleDistribution(t, samples, expected) + + // Make sure that each half of the set matches the expected distribution. + checkSampleSliceDistributions(t, samples, 2, expected) + + // Make sure that each 7th of the set matches the expected distribution. + checkSampleSliceDistributions(t, samples, 7, expected) +} + +// Actual tests + +func TestStandardExponentialValues(t *testing.T) { + for _, seed := range testSeeds { + testExponentialDistribution(t, numTestSamples, 1, seed) + } +} + +func TestNonStandardExponentialValues(t *testing.T) { + for rate := 0.05; rate < 10; rate *= 2 { + for _, seed := range testSeeds { + testExponentialDistribution(t, numTestSamples, rate, seed) + if testing.Short() { + break + } + } + } +} + +// +// Table generation tests +// + +func initNorm() (testKn []uint32, testWn, testFn []float32) { + const m1 = 1 << 31 + var ( + dn float64 = rn + tn = dn + vn float64 = 9.91256303526217e-3 + ) + + testKn = make([]uint32, 128) + testWn = make([]float32, 128) + testFn = make([]float32, 128) + + q := vn / math.Exp(-0.5*dn*dn) + testKn[0] = uint32((dn / q) * m1) + testKn[1] = 0 + testWn[0] = float32(q / m1) + testWn[127] = float32(dn / m1) + testFn[0] = 1.0 + testFn[127] = float32(math.Exp(-0.5 * dn * dn)) + for i := 126; i >= 1; i-- { + dn = math.Sqrt(-2.0 * math.Log(vn/dn+math.Exp(-0.5*dn*dn))) + testKn[i+1] = uint32((dn / tn) * m1) + tn = dn + testFn[i] = float32(math.Exp(-0.5 * dn * dn)) + testWn[i] = float32(dn / m1) + } + return +} + +func initExp() (testKe []uint32, testWe, testFe []float32) { + const m2 = 1 << 32 + var ( + de float64 = re + te = de + ve float64 = 3.9496598225815571993e-3 + ) + + testKe = make([]uint32, 256) + testWe = make([]float32, 256) + testFe = make([]float32, 256) + + q := ve / math.Exp(-de) + testKe[0] = uint32((de / q) * m2) + testKe[1] = 0 + testWe[0] = float32(q / m2) + testWe[255] = float32(de / m2) + testFe[0] = 1.0 + testFe[255] = float32(math.Exp(-de)) + for i := 254; i >= 1; i-- { + de = -math.Log(ve/de + math.Exp(-de)) + testKe[i+1] = uint32((de / te) * m2) + te = de + testFe[i] = float32(math.Exp(-de)) + testWe[i] = float32(de / m2) + } + return +} + +// compareUint32Slices returns the first index where the two slices +// disagree, or <0 if the lengths are the same and all elements +// are identical. +func compareUint32Slices(s1, s2 []uint32) int { + if len(s1) != len(s2) { + if len(s1) > len(s2) { + return len(s2) + 1 + } + return len(s1) + 1 + } + for i := range s1 { + if s1[i] != s2[i] { + return i + } + } + return -1 +} + +// compareFloat32Slices returns the first index where the two slices +// disagree, or <0 if the lengths are the same and all elements +// are identical. +func compareFloat32Slices(s1, s2 []float32) int { + if len(s1) != len(s2) { + if len(s1) > len(s2) { + return len(s2) + 1 + } + return len(s1) + 1 + } + for i := range s1 { + if !nearEqual(float64(s1[i]), float64(s2[i]), 0, 1e-7) { + return i + } + } + return -1 +} + +func TestNormTables(t *testing.T) { + testKn, testWn, testFn := initNorm() + if i := compareUint32Slices(kn[0:], testKn); i >= 0 { + t.Errorf("kn disagrees at index %v; %v != %v", i, kn[i], testKn[i]) + } + if i := compareFloat32Slices(wn[0:], testWn); i >= 0 { + t.Errorf("wn disagrees at index %v; %v != %v", i, wn[i], testWn[i]) + } + if i := compareFloat32Slices(fn[0:], testFn); i >= 0 { + t.Errorf("fn disagrees at index %v; %v != %v", i, fn[i], testFn[i]) + } +} + +func TestExpTables(t *testing.T) { + testKe, testWe, testFe := initExp() + if i := compareUint32Slices(ke[0:], testKe); i >= 0 { + t.Errorf("ke disagrees at index %v; %v != %v", i, ke[i], testKe[i]) + } + if i := compareFloat32Slices(we[0:], testWe); i >= 0 { + t.Errorf("we disagrees at index %v; %v != %v", i, we[i], testWe[i]) + } + if i := compareFloat32Slices(fe[0:], testFe); i >= 0 { + t.Errorf("fe disagrees at index %v; %v != %v", i, fe[i], testFe[i]) + } +} + +func TestFloat32(t *testing.T) { + // For issue 6721, the problem came after 7533753 calls, so check 10e6. + num := int(10e6) + + // XXX: This will take a lot of time on Gno, so reduce by 100 from original. + num /= 100 // 1.72 seconds instead of 172 seconds + + r := testRand() + for ct := 0; ct < num; ct++ { + f := r.Float32() + if f >= 1 { + t.Fatal("Float32() should be in range [0,1). ct:", ct, "f:", f) + } + } +} + +func TestShuffleSmall(t *testing.T) { + // Check that Shuffle allows n=0 and n=1, but that swap is never called for them. + r := testRand() + for n := 0; n <= 1; n++ { + r.Shuffle(n, func(i, j int) { t.Fatalf("swap called, n=%d i=%d j=%d", n, i, j) }) + } +} + +// encodePerm converts from a permuted slice of length n, such as Perm generates, to an int in [0, n!). +// See https://en.wikipedia.org/wiki/Lehmer_code. +// encodePerm modifies the input slice. +func encodePerm(s []int) int { + // Convert to Lehmer code. + for i, x := range s { + r := s[i+1:] + for j, y := range r { + if y > x { + r[j]-- + } + } + } + // Convert to int in [0, n!). + m := 0 + fact := 1 + for i := len(s) - 1; i >= 0; i-- { + m += s[i] * fact + fact *= len(s) - i + } + return m +} + +// TestUniformFactorial tests several ways of generating a uniform value in [0, n!). +func TestUniformFactorial(t *testing.T) { + t.Skip("This test takes ~3 mins to run on Gno at time of writing, even in its short form") + + r := New(NewPCG(1, 2)) + top := 6 + if testing.Short() { + top = 3 + } + for n := 3; n <= top; n++ { + t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) { + // Calculate n!. + nfact := 1 + for i := 2; i <= n; i++ { + nfact *= i + } + + // Test a few different ways to generate a uniform distribution. + p := make([]int, n) // re-usable slice for Shuffle generator + tests := [...]struct { + name string + fn func() int + }{ + {name: "Int32N", fn: func() int { return int(r.Int32N(int32(nfact))) }}, + {name: "Perm", fn: func() int { return encodePerm(r.Perm(n)) }}, + {name: "Shuffle", fn: func() int { + // Generate permutation using Shuffle. + for i := range p { + p[i] = i + } + r.Shuffle(n, func(i, j int) { p[i], p[j] = p[j], p[i] }) + return encodePerm(p) + }}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Gather chi-squared values and check that they follow + // the expected normal distribution given n!-1 degrees of freedom. + // See https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test and + // https://www.johndcook.com/Beautiful_Testing_ch10.pdf. + nsamples := 10 * nfact + if nsamples < 1000 { + nsamples = 1000 + } + samples := make([]float64, nsamples) + for i := range samples { + // Generate some uniformly distributed values and count their occurrences. + const iters = 1000 + counts := make([]int, nfact) + for i := 0; i < iters; i++ { + counts[test.fn()]++ + } + // Calculate chi-squared and add to samples. + want := iters / float64(nfact) + var χ2 float64 + for _, have := range counts { + err := float64(have) - want + χ2 += err * err + } + χ2 /= want + samples[i] = χ2 + } + + // Check that our samples approximate the appropriate normal distribution. + dof := float64(nfact - 1) + expected := &statsResults{mean: dof, stddev: math.Sqrt(2 * dof)} + errorScale := max(1.0, expected.stddev) + expected.closeEnough = 0.10 * errorScale + expected.maxError = 0.08 // TODO: What is the right value here? See issue 21211. + checkSampleDistribution(t, samples, expected) + }) + } + }) + } +} + +// Benchmarks + +var Sink uint64 + +func testRand() *Rand { + return New(NewPCG(1, 2)) +} + +func BenchmarkSourceUint64(b *testing.B) { + s := NewPCG(1, 2) + var t uint64 + for n := b.N; n > 0; n-- { + t += s.Uint64() + } + Sink = uint64(t) +} + +func BenchmarkGlobalInt64(b *testing.B) { + var t int64 + for n := b.N; n > 0; n-- { + t += Int64() + } + Sink = uint64(t) +} + +func BenchmarkGlobalUint64(b *testing.B) { + var t uint64 + for n := b.N; n > 0; n-- { + t += Uint64() + } + Sink = t +} + +func BenchmarkInt64(b *testing.B) { + r := testRand() + var t int64 + for n := b.N; n > 0; n-- { + t += r.Int64() + } + Sink = uint64(t) +} + +func BenchmarkUint64(b *testing.B) { + r := testRand() + var t uint64 + for n := b.N; n > 0; n-- { + t += r.Uint64() + } + Sink = t +} + +func BenchmarkGlobalIntN1000(b *testing.B) { + var t int + arg := 1000 + for n := b.N; n > 0; n-- { + t += IntN(arg) + } + Sink = uint64(t) +} + +func BenchmarkIntN1000(b *testing.B) { + r := testRand() + var t int + arg := 1000 + for n := b.N; n > 0; n-- { + t += r.IntN(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N1000(b *testing.B) { + r := testRand() + var t int64 + arg := int64(1000) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N1e8(b *testing.B) { + r := testRand() + var t int64 + arg := int64(1e8) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N1e9(b *testing.B) { + r := testRand() + var t int64 + arg := int64(1e9) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N2e9(b *testing.B) { + r := testRand() + var t int64 + arg := int64(2e9) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N1e18(b *testing.B) { + r := testRand() + var t int64 + arg := int64(1e18) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N2e18(b *testing.B) { + r := testRand() + var t int64 + arg := int64(2e18) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt64N4e18(b *testing.B) { + r := testRand() + var t int64 + arg := int64(4e18) + for n := b.N; n > 0; n-- { + t += r.Int64N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt32N1000(b *testing.B) { + r := testRand() + var t int32 + arg := int32(1000) + for n := b.N; n > 0; n-- { + t += r.Int32N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt32N1e8(b *testing.B) { + r := testRand() + var t int32 + arg := int32(1e8) + for n := b.N; n > 0; n-- { + t += r.Int32N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt32N1e9(b *testing.B) { + r := testRand() + var t int32 + arg := int32(1e9) + for n := b.N; n > 0; n-- { + t += r.Int32N(arg) + } + Sink = uint64(t) +} + +func BenchmarkInt32N2e9(b *testing.B) { + r := testRand() + var t int32 + arg := int32(2e9) + for n := b.N; n > 0; n-- { + t += r.Int32N(arg) + } + Sink = uint64(t) +} + +func BenchmarkFloat32(b *testing.B) { + r := testRand() + var t float32 + for n := b.N; n > 0; n-- { + t += r.Float32() + } + Sink = uint64(t) +} + +func BenchmarkFloat64(b *testing.B) { + r := testRand() + var t float64 + for n := b.N; n > 0; n-- { + t += r.Float64() + } + Sink = uint64(t) +} + +func BenchmarkExpFloat64(b *testing.B) { + r := testRand() + var t float64 + for n := b.N; n > 0; n-- { + t += r.ExpFloat64() + } + Sink = uint64(t) +} + +func BenchmarkNormFloat64(b *testing.B) { + r := testRand() + var t float64 + for n := b.N; n > 0; n-- { + t += r.NormFloat64() + } + Sink = uint64(t) +} + +func BenchmarkPerm3(b *testing.B) { + r := testRand() + var t int + for n := b.N; n > 0; n-- { + t += r.Perm(3)[0] + } + Sink = uint64(t) +} + +func BenchmarkPerm30(b *testing.B) { + r := testRand() + var t int + for n := b.N; n > 0; n-- { + t += r.Perm(30)[0] + } + Sink = uint64(t) +} + +func BenchmarkPerm30ViaShuffle(b *testing.B) { + r := testRand() + var t int + for n := b.N; n > 0; n-- { + p := make([]int, 30) + for i := range p { + p[i] = i + } + r.Shuffle(30, func(i, j int) { p[i], p[j] = p[j], p[i] }) + t += p[0] + } + Sink = uint64(t) +} + +// BenchmarkShuffleOverhead uses a minimal swap function +// to measure just the shuffling overhead. +func BenchmarkShuffleOverhead(b *testing.B) { + r := testRand() + for n := b.N; n > 0; n-- { + r.Shuffle(30, func(i, j int) { + if i < 0 || i >= 30 || j < 0 || j >= 30 { + b.Fatalf("bad swap(%d, %d)", i, j) + } + }) + } +} diff --git a/gnovm/stdlibs/math/rand/regress_test.gno b/gnovm/stdlibs/math/rand/regress_test.gno new file mode 100644 index 00000000000..6ef6e1e0bf8 --- /dev/null +++ b/gnovm/stdlibs/math/rand/regress_test.gno @@ -0,0 +1,469 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test that random number sequences generated by a specific seed +// do not change from version to version. +// +// Do NOT make changes to the golden outputs. If bugs need to be fixed +// in the underlying code, find ways to fix them that do not affect the +// outputs. + +package rand + +/* +XXX: disabled because reflect, but would be good to enable it +switching reflect to simple fn calls. + +func TestRegress(t *testing.T) { + int32s := []int32{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1} + uint32s := []uint32{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1, 1<<32 - 2, 1<<32 - 1} + int64s := []int64{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1, 1000000000000000000, 1 << 60, 1<<63 - 2, 1<<63 - 1} + uint64s := []uint64{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1, 1000000000000000000, 1 << 60, 1<<63 - 2, 1<<63 - 1, 1<<64 - 2, 1<<64 - 1} + permSizes := []int{0, 1, 5, 8, 9, 10, 16} + + n := reflect.TypeOf(New(NewPCG(1, 2))).NumMethod() + p := 0 + var buf bytes.Buffer + for i := 0; i < n; i++ { + if *update && i > 0 { + fmt.Fprintf(&buf, "\n") + } + r := New(NewPCG(1, 2)) + rv := reflect.ValueOf(r) + m := rv.Type().Method(i) + mv := rv.Method(i) + mt := mv.Type() + if mt.NumOut() == 0 { + continue + } + for repeat := 0; repeat < 20; repeat++ { + var args []reflect.Value + var argstr string + if mt.NumIn() == 1 { + var x any + switch mt.In(0).Kind() { + default: + t.Fatalf("unexpected argument type for r.%s", m.Name) + + case reflect.Int: + if m.Name == "Perm" { + x = permSizes[repeat%len(permSizes)] + break + } + big := int64s[repeat%len(int64s)] + if int64(int(big)) != big { + // On 32-bit machine. + // Consume an Int64 like on a 64-bit machine, + // to keep the golden data the same on different architectures. + r.Int64N(big) + if *update { + t.Fatalf("must run -update on 64-bit machine") + } + p++ + continue + } + x = int(big) + + case reflect.Uint: + big := uint64s[repeat%len(uint64s)] + if uint64(uint(big)) != big { + r.Uint64N(big) // what would happen on 64-bit machine, to keep stream in sync + if *update { + t.Fatalf("must run -update on 64-bit machine") + } + p++ + continue + } + x = uint(big) + + case reflect.Int32: + x = int32s[repeat%len(int32s)] + + case reflect.Int64: + x = int64s[repeat%len(int64s)] + + case reflect.Uint32: + x = uint32s[repeat%len(uint32s)] + + case reflect.Uint64: + x = uint64s[repeat%len(uint64s)] + } + argstr = fmt.Sprint(x) + args = append(args, reflect.ValueOf(x)) + } + + var out any + out = mv.Call(args)[0].Interface() + if m.Name == "Int" || m.Name == "IntN" { + out = int64(out.(int)) + } + if m.Name == "Uint" || m.Name == "UintN" { + out = uint64(out.(uint)) + } + if *update { + var val string + big := int64(1 << 60) + if int64(int(big)) != big && (m.Name == "Int" || m.Name == "IntN") { + // 32-bit machine cannot print 64-bit results + val = "truncated" + } else if reflect.TypeOf(out).Kind() == reflect.Slice { + val = fmt.Sprintf("%#v", out) + } else { + val = fmt.Sprintf("%T(%v)", out, out) + } + fmt.Fprintf(&buf, "\t%s, // %s(%s)\n", val, m.Name, argstr) + } else if p >= len(regressGolden) { + t.Errorf("r.%s(%s) = %v, missing golden value", m.Name, argstr, out) + } else { + want := regressGolden[p] + if m.Name == "Int" { + want = int64(int(uint(want.(int64)) << 1 >> 1)) + } + if !reflect.DeepEqual(out, want) { + t.Errorf("r.%s(%s) = %v, want %v", m.Name, argstr, out, want) + } + } + p++ + } + } +} +*/ + +var regressGolden = []interface{}{ + float64(0.5931317151369719), // ExpFloat64() + float64(0.0680034588807843), // ExpFloat64() + float64(0.036496967459790364), // ExpFloat64() + float64(2.460335459645379), // ExpFloat64() + float64(1.5792300208419903), // ExpFloat64() + float64(0.9149501499404387), // ExpFloat64() + float64(0.43463410545541104), // ExpFloat64() + float64(0.5513632046504593), // ExpFloat64() + float64(0.7426404617374481), // ExpFloat64() + float64(1.2334925132631804), // ExpFloat64() + float64(0.892529142200442), // ExpFloat64() + float64(0.21508763681487764), // ExpFloat64() + float64(1.0208588200798545), // ExpFloat64() + float64(0.7650739736831382), // ExpFloat64() + float64(0.7772788529257701), // ExpFloat64() + float64(1.102732861281323), // ExpFloat64() + float64(0.6982243043885805), // ExpFloat64() + float64(0.4981788638202421), // ExpFloat64() + float64(0.15806532306947937), // ExpFloat64() + float64(0.9419163802459202), // ExpFloat64() + + float32(0.95955694), // Float32() + float32(0.8076733), // Float32() + float32(0.8135684), // Float32() + float32(0.92872405), // Float32() + float32(0.97472525), // Float32() + float32(0.5485458), // Float32() + float32(0.97740936), // Float32() + float32(0.042272687), // Float32() + float32(0.99663067), // Float32() + float32(0.035181105), // Float32() + float32(0.45059562), // Float32() + float32(0.86597633), // Float32() + float32(0.8954844), // Float32() + float32(0.090798736), // Float32() + float32(0.46218646), // Float32() + float32(0.5955118), // Float32() + float32(0.08985227), // Float32() + float32(0.19820237), // Float32() + float32(0.7443699), // Float32() + float32(0.56461), // Float32() + + float64(0.6764556596678251), // Float64() + float64(0.4613862177205994), // Float64() + float64(0.5085473976760264), // Float64() + float64(0.4297927436037299), // Float64() + float64(0.797802349388613), // Float64() + float64(0.3883664855410056), // Float64() + float64(0.8192750264193612), // Float64() + float64(0.3381816951746133), // Float64() + float64(0.9730458047755973), // Float64() + float64(0.281449117585586), // Float64() + float64(0.6047654075331631), // Float64() + float64(0.9278107175107462), // Float64() + float64(0.16387541502137226), // Float64() + float64(0.7263900707339023), // Float64() + float64(0.6974917552729882), // Float64() + float64(0.7640946923790318), // Float64() + float64(0.7188183661358182), // Float64() + float64(0.5856191500346635), // Float64() + float64(0.9549597149363428), // Float64() + float64(0.5168804691962643), // Float64() + + int64(4969059760275911952), // Int() + int64(2147869220224756844), // Int() + int64(5246770554000605320), // Int() + int64(5471241176507662746), // Int() + int64(4321634407747778896), // Int() + int64(760102831717374652), // Int() + int64(9221744211007427193), // Int() + int64(8289669384274456462), // Int() + int64(2449715415482412441), // Int() + int64(3389241988064777392), // Int() + int64(2986830195847294191), // Int() + int64(8204908297817606218), // Int() + int64(8134976985547166651), // Int() + int64(2240328155279531677), // Int() + int64(7311121042813227358), // Int() + int64(5231057920893523323), // Int() + int64(4257872588489500903), // Int() + int64(158397175702351138), // Int() + int64(1350674201389090105), // Int() + int64(6093522341581845358), // Int() + + int32(1652216515), // Int32() + int32(1323786710), // Int32() + int32(1684546306), // Int32() + int32(1710678126), // Int32() + int32(503104460), // Int32() + int32(88487615), // Int32() + int32(1073552320), // Int32() + int32(965044529), // Int32() + int32(285184408), // Int32() + int32(394559696), // Int32() + int32(1421454622), // Int32() + int32(955177040), // Int32() + int32(2020777787), // Int32() + int32(260808523), // Int32() + int32(851126509), // Int32() + int32(1682717115), // Int32() + int32(1569423431), // Int32() + int32(1092181682), // Int32() + int32(157239171), // Int32() + int32(709379364), // Int32() + + int32(0), // Int32N(1) + int32(6), // Int32N(10) + int32(8), // Int32N(32) + int32(704922), // Int32N(1048576) + int32(245656), // Int32N(1048577) + int32(41205257), // Int32N(1000000000) + int32(43831929), // Int32N(1073741824) + int32(965044528), // Int32N(2147483646) + int32(285184408), // Int32N(2147483647) + int32(0), // Int32N(1) + int32(6), // Int32N(10) + int32(10), // Int32N(32) + int32(283579), // Int32N(1048576) + int32(127348), // Int32N(1048577) + int32(396336665), // Int32N(1000000000) + int32(911873403), // Int32N(1073741824) + int32(1569423430), // Int32N(2147483646) + int32(1092181681), // Int32N(2147483647) + int32(0), // Int32N(1) + int32(3), // Int32N(10) + + int64(4969059760275911952), // Int64() + int64(2147869220224756844), // Int64() + int64(5246770554000605320), // Int64() + int64(5471241176507662746), // Int64() + int64(4321634407747778896), // Int64() + int64(760102831717374652), // Int64() + int64(9221744211007427193), // Int64() + int64(8289669384274456462), // Int64() + int64(2449715415482412441), // Int64() + int64(3389241988064777392), // Int64() + int64(2986830195847294191), // Int64() + int64(8204908297817606218), // Int64() + int64(8134976985547166651), // Int64() + int64(2240328155279531677), // Int64() + int64(7311121042813227358), // Int64() + int64(5231057920893523323), // Int64() + int64(4257872588489500903), // Int64() + int64(158397175702351138), // Int64() + int64(1350674201389090105), // Int64() + int64(6093522341581845358), // Int64() + + int64(0), // Int64N(1) + int64(6), // Int64N(10) + int64(8), // Int64N(32) + int64(704922), // Int64N(1048576) + int64(245656), // Int64N(1048577) + int64(41205257), // Int64N(1000000000) + int64(43831929), // Int64N(1073741824) + int64(965044528), // Int64N(2147483646) + int64(285184408), // Int64N(2147483647) + int64(183731176326946086), // Int64N(1000000000000000000) + int64(680987186633600239), // Int64N(1152921504606846976) + int64(4102454148908803108), // Int64N(9223372036854775806) + int64(8679174511200971228), // Int64N(9223372036854775807) + int64(0), // Int64N(1) + int64(3), // Int64N(10) + int64(27), // Int64N(32) + int64(665831), // Int64N(1048576) + int64(533292), // Int64N(1048577) + int64(73220195), // Int64N(1000000000) + int64(686060398), // Int64N(1073741824) + + int64(0), // IntN(1) + int64(6), // IntN(10) + int64(8), // IntN(32) + int64(704922), // IntN(1048576) + int64(245656), // IntN(1048577) + int64(41205257), // IntN(1000000000) + int64(43831929), // IntN(1073741824) + int64(965044528), // IntN(2147483646) + int64(285184408), // IntN(2147483647) + int64(183731176326946086), // IntN(1000000000000000000) + int64(680987186633600239), // IntN(1152921504606846976) + int64(4102454148908803108), // IntN(9223372036854775806) + int64(8679174511200971228), // IntN(9223372036854775807) + int64(0), // IntN(1) + int64(3), // IntN(10) + int64(27), // IntN(32) + int64(665831), // IntN(1048576) + int64(533292), // IntN(1048577) + int64(73220195), // IntN(1000000000) + int64(686060398), // IntN(1073741824) + + float64(0.37944549835531083), // NormFloat64() + float64(0.07473804659119399), // NormFloat64() + float64(0.20006841200604142), // NormFloat64() + float64(-1.1253144115495104), // NormFloat64() + float64(-0.4005883316435388), // NormFloat64() + float64(-3.0853771402394736), // NormFloat64() + float64(1.932330243076978), // NormFloat64() + float64(1.726131393719264), // NormFloat64() + float64(-0.11707238034168332), // NormFloat64() + float64(-0.9303318111676635), // NormFloat64() + float64(-0.04750789419852852), // NormFloat64() + float64(0.22248301107582735), // NormFloat64() + float64(-1.83630520614272), // NormFloat64() + float64(0.7259521217919809), // NormFloat64() + float64(0.8806882871913041), // NormFloat64() + float64(-1.5022903484270484), // NormFloat64() + float64(0.5972577266810571), // NormFloat64() + float64(1.5631937339973658), // NormFloat64() + float64(-0.3841235370075905), // NormFloat64() + float64(-0.2967295854430667), // NormFloat64() + + []int{}, // Perm(0) + []int{0}, // Perm(1) + []int{1, 4, 2, 0, 3}, // Perm(5) + []int{4, 3, 6, 1, 5, 2, 7, 0}, // Perm(8) + []int{6, 5, 1, 8, 7, 2, 0, 3, 4}, // Perm(9) + []int{9, 4, 2, 5, 6, 8, 1, 7, 0, 3}, // Perm(10) + []int{5, 9, 3, 1, 4, 2, 10, 7, 15, 11, 0, 14, 13, 8, 6, 12}, // Perm(16) + []int{}, // Perm(0) + []int{0}, // Perm(1) + []int{4, 2, 1, 3, 0}, // Perm(5) + []int{0, 2, 3, 1, 5, 4, 6, 7}, // Perm(8) + []int{2, 0, 8, 3, 4, 7, 6, 5, 1}, // Perm(9) + []int{0, 6, 5, 3, 8, 4, 1, 2, 9, 7}, // Perm(10) + []int{9, 14, 4, 11, 13, 8, 0, 6, 2, 12, 3, 7, 1, 10, 5, 15}, // Perm(16) + []int{}, // Perm(0) + []int{0}, // Perm(1) + []int{2, 4, 0, 3, 1}, // Perm(5) + []int{3, 2, 1, 0, 7, 5, 4, 6}, // Perm(8) + []int{1, 3, 4, 5, 0, 2, 7, 8, 6}, // Perm(9) + []int{1, 8, 4, 7, 2, 6, 5, 9, 0, 3}, // Perm(10) + + uint32(3304433030), // Uint32() + uint32(2647573421), // Uint32() + uint32(3369092613), // Uint32() + uint32(3421356252), // Uint32() + uint32(1006208920), // Uint32() + uint32(176975231), // Uint32() + uint32(2147104640), // Uint32() + uint32(1930089058), // Uint32() + uint32(570368816), // Uint32() + uint32(789119393), // Uint32() + uint32(2842909244), // Uint32() + uint32(1910354080), // Uint32() + uint32(4041555575), // Uint32() + uint32(521617046), // Uint32() + uint32(1702253018), // Uint32() + uint32(3365434230), // Uint32() + uint32(3138846863), // Uint32() + uint32(2184363364), // Uint32() + uint32(314478343), // Uint32() + uint32(1418758728), // Uint32() + + uint32(0), // Uint32N(1) + uint32(6), // Uint32N(10) + uint32(8), // Uint32N(32) + uint32(704922), // Uint32N(1048576) + uint32(245656), // Uint32N(1048577) + uint32(41205257), // Uint32N(1000000000) + uint32(43831929), // Uint32N(1073741824) + uint32(965044528), // Uint32N(2147483646) + uint32(285184408), // Uint32N(2147483647) + uint32(789119393), // Uint32N(4294967294) + uint32(2842909244), // Uint32N(4294967295) + uint32(0), // Uint32N(1) + uint32(9), // Uint32N(10) + uint32(29), // Uint32N(32) + uint32(266590), // Uint32N(1048576) + uint32(821640), // Uint32N(1048577) + uint32(730819735), // Uint32N(1000000000) + uint32(522841378), // Uint32N(1073741824) + uint32(157239171), // Uint32N(2147483646) + uint32(709379364), // Uint32N(2147483647) + + uint64(14192431797130687760), // Uint64() + uint64(11371241257079532652), // Uint64() + uint64(14470142590855381128), // Uint64() + uint64(14694613213362438554), // Uint64() + uint64(4321634407747778896), // Uint64() + uint64(760102831717374652), // Uint64() + uint64(9221744211007427193), // Uint64() + uint64(8289669384274456462), // Uint64() + uint64(2449715415482412441), // Uint64() + uint64(3389241988064777392), // Uint64() + uint64(12210202232702069999), // Uint64() + uint64(8204908297817606218), // Uint64() + uint64(17358349022401942459), // Uint64() + uint64(2240328155279531677), // Uint64() + uint64(7311121042813227358), // Uint64() + uint64(14454429957748299131), // Uint64() + uint64(13481244625344276711), // Uint64() + uint64(9381769212557126946), // Uint64() + uint64(1350674201389090105), // Uint64() + uint64(6093522341581845358), // Uint64() + + uint64(0), // Uint64N(1) + uint64(6), // Uint64N(10) + uint64(8), // Uint64N(32) + uint64(704922), // Uint64N(1048576) + uint64(245656), // Uint64N(1048577) + uint64(41205257), // Uint64N(1000000000) + uint64(43831929), // Uint64N(1073741824) + uint64(965044528), // Uint64N(2147483646) + uint64(285184408), // Uint64N(2147483647) + uint64(183731176326946086), // Uint64N(1000000000000000000) + uint64(680987186633600239), // Uint64N(1152921504606846976) + uint64(4102454148908803108), // Uint64N(9223372036854775806) + uint64(8679174511200971228), // Uint64N(9223372036854775807) + uint64(2240328155279531676), // Uint64N(18446744073709551614) + uint64(7311121042813227357), // Uint64N(18446744073709551615) + uint64(0), // Uint64N(1) + uint64(7), // Uint64N(10) + uint64(2), // Uint64N(32) + uint64(312633), // Uint64N(1048576) + uint64(346376), // Uint64N(1048577) + + uint64(0), // UintN(1) + uint64(6), // UintN(10) + uint64(8), // UintN(32) + uint64(704922), // UintN(1048576) + uint64(245656), // UintN(1048577) + uint64(41205257), // UintN(1000000000) + uint64(43831929), // UintN(1073741824) + uint64(965044528), // UintN(2147483646) + uint64(285184408), // UintN(2147483647) + uint64(183731176326946086), // UintN(1000000000000000000) + uint64(680987186633600239), // UintN(1152921504606846976) + uint64(4102454148908803108), // UintN(9223372036854775806) + uint64(8679174511200971228), // UintN(9223372036854775807) + uint64(2240328155279531676), // UintN(18446744073709551614) + uint64(7311121042813227357), // UintN(18446744073709551615) + uint64(0), // UintN(1) + uint64(7), // UintN(10) + uint64(2), // UintN(32) + uint64(312633), // UintN(1048576) + uint64(346376), // UintN(1048577) +} diff --git a/gnovm/stdlibs/math/rand/zipf.gno b/gnovm/stdlibs/math/rand/zipf.gno new file mode 100644 index 00000000000..f04c814eb75 --- /dev/null +++ b/gnovm/stdlibs/math/rand/zipf.gno @@ -0,0 +1,77 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// W.Hormann, G.Derflinger: +// "Rejection-Inversion to Generate Variates +// from Monotone Discrete Distributions" +// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz + +package rand + +import "math" + +// A Zipf generates Zipf distributed variates. +type Zipf struct { + r *Rand + imax float64 + v float64 + q float64 + s float64 + oneminusQ float64 + oneminusQinv float64 + hxm float64 + hx0minusHxm float64 +} + +func (z *Zipf) h(x float64) float64 { + return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv +} + +func (z *Zipf) hinv(x float64) float64 { + return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v +} + +// NewZipf returns a Zipf variate generator. +// The generator generates values k ∈ [0, imax] +// such that P(k) is proportional to (v + k) ** (-s). +// Requirements: s > 1 and v >= 1. +func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf { + z := new(Zipf) + if s <= 1.0 || v < 1 { + return nil + } + z.r = r + z.imax = float64(imax) + z.v = v + z.q = s + z.oneminusQ = 1.0 - z.q + z.oneminusQinv = 1.0 / z.oneminusQ + z.hxm = z.h(z.imax + 0.5) + z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm + z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0))) + return z +} + +// Uint64 returns a value drawn from the Zipf distribution described +// by the Zipf object. +func (z *Zipf) Uint64() uint64 { + if z == nil { + panic("rand: nil Zipf") + } + k := 0.0 + + for { + r := z.r.Float64() // r on [0,1] + ur := z.hxm + r*z.hx0minusHxm + x := z.hinv(ur) + k = math.Floor(x + 0.5) + if k-x <= z.s { + break + } + if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) { + break + } + } + return uint64(k) +} diff --git a/gnovm/stdlibs/net/url/url.gno b/gnovm/stdlibs/net/url/url.gno index 501b263e873..fb123ad1493 100644 --- a/gnovm/stdlibs/net/url/url.gno +++ b/gnovm/stdlibs/net/url/url.gno @@ -12,7 +12,6 @@ package url import ( "errors" - "fmt" "path" "sort" "strconv" @@ -27,7 +26,7 @@ type Error struct { } func (e *Error) Unwrap() error { return e.Err } -func (e *Error) Error() string { return fmt.Sprintf("%s %q: %s", e.Op, e.URL, e.Err) } +func (e *Error) Error() string { return e.Op + " \"" + e.URL + "\": " + e.Err.Error() } // "%s %q: %s" func (e *Error) Timeout() bool { t, ok := e.Err.(interface { @@ -623,7 +622,7 @@ func parseHost(host string) (string, error) { } colonPort := host[i+1:] if !validOptionalPort(colonPort) { - return "", fmt.Errorf("invalid port %q after host", colonPort) + return "", errors.New("invalid port \"" + colonPort + "\" after host") } // RFC 6874 defines that %25 (%-encoded percent) introduces @@ -651,7 +650,7 @@ func parseHost(host string) (string, error) { } else if i := strings.LastIndex(host, ":"); i != -1 { colonPort := host[i:] if !validOptionalPort(colonPort) { - return "", fmt.Errorf("invalid port %q after host", colonPort) + return "", errors.New("invalid port \"" + colonPort + "\" after host") } } @@ -934,7 +933,7 @@ func parseQuery(m Values, query string) (err error) { var key string key, query, _ = strings.Cut(query, "&") if strings.Contains(key, ";") { - err = fmt.Errorf("invalid semicolon separator in query") + err = errors.New("invalid semicolon separator in query") continue } if key == "" { diff --git a/gnovm/stdlibs/net/url/url_test.gno b/gnovm/stdlibs/net/url/url_test.gno index 0471a0a9e85..9e173becc0b 100644 --- a/gnovm/stdlibs/net/url/url_test.gno +++ b/gnovm/stdlibs/net/url/url_test.gno @@ -5,12 +5,13 @@ package url import ( - "bytes" + // encodingPkg "encoding" // "encoding/gob" "encoding/json" "fmt" "io" + // "net" "strings" "testing" diff --git a/gnovm/stdlibs/sort/sort_test.gno b/gnovm/stdlibs/sort/sort_test.gno index 8416f0b77e1..dac6ab11f67 100644 --- a/gnovm/stdlibs/sort/sort_test.gno +++ b/gnovm/stdlibs/sort/sort_test.gno @@ -98,7 +98,7 @@ func TestSortLarge_Random(t *testing.T) { } data := make([]int, n) for i := 0; i < len(data); i++ { - data[i] = rand.Intn(100) + data[i] = rand.IntN(100) } if sort.IntsAreSorted(data) { t.Fatalf("terrible rand.rand") @@ -160,7 +160,7 @@ func TestNonDeterministicComparison(t *testing.T) { }() td := &nonDeterministicTestingData{ - r: rand.New(rand.NewSource(0)), + r: rand.New(rand.NewPCG(0, 0)), } for i := 0; i < 10; i++ { @@ -377,13 +377,13 @@ func testBentleyMcIlroy(t *testing.T, sortFn func(sort.Interface), maxswap func( case _Sawtooth: data[i] = i % m case _Rand: - data[i] = rand.Intn(m) + data[i] = rand.IntN(m) case _Stagger: data[i] = (i*m + i) % n case _Plateau: data[i] = min(i, m) case _Shuffle: - if rand.Intn(m) != 0 { + if rand.IntN(m) != 0 { j += 2 data[i] = j } else { @@ -587,7 +587,7 @@ func TestStability(t *testing.T) { // random distribution for i := 0; i < len(data); i++ { - data[i].a = rand.Intn(m) + data[i].a = rand.IntN(m) } if sort.IsSorted(data) { t.Fatalf("terrible rand.rand") @@ -643,7 +643,7 @@ func countOps(t *testing.T, algo func(sort.Interface), name string) { maxswap: 1<<31 - 1, } for i := 0; i < n; i++ { - td.data[i] = rand.Intn(n / 5) + td.data[i] = rand.IntN(n / 5) } algo(&td) t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp) diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 958fea369a3..2b94292bd7e 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -1,6 +1,8 @@ package std -import "strconv" +import ( + "strconv" +) // Realm functions can call std.GetBanker(options) to get // a banker instance. Banker objects cannot be persisted, @@ -66,7 +68,20 @@ func GetBanker(bt BankerType) Banker { if bt >= maxBanker { panic("invalid banker type") } - return banker{bt} + + 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") + } + } else if bt == BankerTypeRealmSend || bt == BankerTypeRealmIssue { + pkgAddr = CurrentRealm().Addr() + } + return banker{ + bt, + pkgAddr, + } } // These are native bindings to the banker's functions. @@ -77,7 +92,8 @@ func bankerIssueCoin(bt uint8, addr string, denom string, amount int64) func bankerRemoveCoin(bt uint8, addr string, denom string, amount int64) type banker struct { - bt BankerType + bt BankerType + pkgAddr Address } func (b banker) GetCoins(addr Address) (dst Coins) { @@ -93,6 +109,10 @@ func (b banker) SendCoins(from, to Address, amt Coins) { if b.bt == BankerTypeReadonly { panic("BankerTypeReadonly cannot send coins") } + if b.pkgAddr != from { + msg := `can only send coins from realm that created banker "` + b.pkgAddr + `", not "` + from + `"` + panic(msg) + } denoms, amounts := amt.expandNative() bankerSendCoins(uint8(b.bt), string(from), string(to), denoms, amounts) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 6eb7e720b87..892af94777f 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -37,34 +37,17 @@ const ( var reDenom = regexp.MustCompile("[a-z][a-z0-9]{2,15}") func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { - coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address(addr)) + coins := GetContext(m).Banker.GetCoins(crypto.Bech32Address(addr)) return ExpandCoins(coins) } func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []string, amounts []int64) { // bt != BankerTypeReadonly (checked in gno) - ctx := m.Context.(ExecContext) + ctx := GetContext(m) amt := CompactCoins(denoms, amounts) from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) - pkgAddr := ctx.OrigPkgAddr - if m.Realm != nil { - pkgPath := m.Realm.Path - pkgAddr = gno.DerivePkgAddr(pkgPath).Bech32() - } - - if bt == btOrigSend || bt == btRealmSend { - if from != pkgAddr { - m.Panic(typedString( - fmt.Sprintf( - "can only send from the realm package address %q, but got %q", - pkgAddr, from), - )) - return - } - } - switch bt { case btOrigSend: // indirection allows us to "commit" in a second phase @@ -87,7 +70,7 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str } func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { - return m.Context.(ExecContext).Banker.TotalCoin(denom) + return GetContext(m).Banker.TotalCoin(denom) } func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { @@ -104,7 +87,7 @@ func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amou // ibc_denom := 'ibc/' + hash('path' + 'base_denom') // gno_realm_denom := '/' + 'pkg_path' + ':' + 'base_denom' newDenom := "/" + m.Realm.Path + ":" + denom - m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), newDenom, amount) + GetContext(m).Banker.IssueCoin(crypto.Bech32Address(addr), newDenom, amount) } func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { @@ -117,5 +100,5 @@ func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amo } newDenom := "/" + m.Realm.Path + ":" + denom - m.Context.(ExecContext).Banker.RemoveCoin(crypto.Bech32Address(addr), newDenom, amount) + GetContext(m).Banker.RemoveCoin(crypto.Bech32Address(addr), newDenom, amount) } diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index dd138d30822..47e88e238d2 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -1,33 +1,141 @@ package std -import "strconv" +import ( + "math/overflow" + "strconv" +) -// NOTE: this is selectly copied over from pkgs/std/coin.go -// TODO: import all functionality(?). -// TODO: implement Coin/Coins constructors. +// NOTE: this is selectively copied over from tm2/pkgs/std/coin.go -// Coin hold some amount of one currency. +// Coin holds some amount of one currency. // A negative amount is invalid. type Coin struct { Denom string `json:"denom"` Amount int64 `json:"amount"` } +// NewCoin returns a new coin with a denomination and amount +func NewCoin(denom string, amount int64) Coin { + return Coin{ + Denom: denom, + Amount: amount, + } +} + +// String provides a human-readable representation of a coin func (c Coin) String() string { return strconv.Itoa(int(c.Amount)) + c.Denom } +// IsGTE returns true if they are the same type and the receiver is +// an equal or greater value func (c Coin) IsGTE(other Coin) bool { - if c.Denom != other.Denom { - panic("invalid coin denominations: " + c.Denom) - } + mustMatchDenominations(c.Denom, other.Denom) + return c.Amount >= other.Amount } +// IsLT returns true if they are the same type and the receiver is +// a smaller value +func (c Coin) IsLT(other Coin) bool { + mustMatchDenominations(c.Denom, other.Denom) + + return c.Amount < other.Amount +} + +// IsEqual returns true if the two sets of Coins have the same value +func (c Coin) IsEqual(other Coin) bool { + mustMatchDenominations(c.Denom, other.Denom) + + return c.Amount == other.Amount +} + +// Add adds amounts of two coins with same denom. +// If the coins differ in denom then it panics. +// An overflow or underflow panics. +// An invalid result panics. +func (c Coin) Add(other Coin) Coin { + mustMatchDenominations(c.Denom, other.Denom) + + sum, ok := overflow.Add64(c.Amount, other.Amount) + if !ok { + panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + + c.Amount = sum + return c +} + +// Sub subtracts amounts of two coins with same denom. +// If the coins differ in denom then it panics. +// An overflow or underflow panics. +// An invalid result panics. +func (c Coin) Sub(other Coin) Coin { + mustMatchDenominations(c.Denom, other.Denom) + + dff, ok := overflow.Sub64(c.Amount, other.Amount) + if !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + c.Amount = dff + + return c +} + +// IsPositive returns true if coin amount is positive. +func (c Coin) IsPositive() bool { + return c.Amount > 0 +} + +// IsNegative returns true if the coin amount is negative and false otherwise. +func (c Coin) IsNegative() bool { + return c.Amount < 0 +} + +// IsZero returns true if the amount of given coin is zero +func (c Coin) IsZero() bool { + return c.Amount == 0 +} + +func mustMatchDenominations(denomA, denomB string) { + if denomA != denomB { + panic("incompatible coin denominations: " + denomA + ", " + denomB) + } +} + // Coins is a set of Coin, one per currency type Coins []Coin +// NewCoins returns a new set of Coins given one or more Coins +// Consolidates any denom duplicates into one, keeping the properties of a mathematical set +func NewCoins(coins ...Coin) Coins { + coinMap := make(map[string]int64) + + for _, coin := range coins { + if currentAmount, exists := coinMap[coin.Denom]; exists { + var ok bool + if coinMap[coin.Denom], ok = overflow.Add64(currentAmount, coin.Amount); !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(currentAmount)) + " +/- " + strconv.Itoa(int(coin.Amount))) + } + } else { + coinMap[coin.Denom] = coin.Amount + } + } + + var setCoins Coins + for denom, amount := range coinMap { + setCoins = append(setCoins, NewCoin(denom, amount)) + } + + return setCoins +} + +// String returns the string representation of Coins func (cz Coins) String() string { + if len(cz) == 0 { + return "" + } + res := "" for i, c := range cz { if i > 0 { @@ -35,35 +143,41 @@ func (cz Coins) String() string { } res += c.String() } + return res } +// AmountOf returns the amount of a specific coin from the Coins set func (cz Coins) AmountOf(denom string) int64 { for _, c := range cz { if c.Denom == denom { return c.Amount } } + return 0 } -func (a Coins) Add(b Coins) Coins { +// Add adds a Coin to the Coins set +func (cz Coins) Add(b Coins) Coins { c := Coins{} - for _, ac := range a { + for _, ac := range cz { bc := b.AmountOf(ac.Denom) ac.Amount += bc c = append(c, ac) } + for _, bc := range b { cc := c.AmountOf(bc.Denom) if cc == 0 { c = append(c, bc) } } + return c } -// expand for usage within natively bound functions. +// expandNative expands for usage within natively bound functions. func (cz Coins) expandNative() (denoms []string, amounts []int64) { denoms = make([]string, len(cz)) amounts = make([]int64, len(cz)) @@ -71,5 +185,6 @@ func (cz Coins) expandNative() (denoms []string, amounts []int64) { denoms[i] = coin.Denom amounts[i] = coin.Amount } + return denoms, amounts } diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index 35aef7fbd62..ff5c91a14eb 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -1,6 +1,7 @@ package std import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" @@ -19,3 +20,28 @@ type ExecContext struct { Banker BankerInterface EventLogger *sdk.EventLogger } + +// GetContext returns the execution context. +// This is used to allow extending the exec context using interfaces, +// for instance when testing. +func (e ExecContext) GetExecContext() ExecContext { + return e +} + +var _ ExecContexter = ExecContext{} + +// ExecContexter is a type capable of returning the parent [ExecContext]. When +// using these standard libraries, m.Context should always implement this +// interface. This can be obtained by embedding [ExecContext]. +type ExecContexter interface { + GetExecContext() ExecContext +} + +// NOTE: In order to make this work by simply embedding ExecContext in another +// context (like TestExecContext), the method needs to be something other than +// the field name. + +// GetContext returns the context from the Gno machine. +func GetContext(m *gno.Machine) ExecContext { + return m.Context.(ExecContexter).GetExecContext() +} diff --git a/gnovm/stdlibs/std/emit_event.gno b/gnovm/stdlibs/std/emit_event.gno index 147513e962b..1c77f903d88 100644 --- a/gnovm/stdlibs/std/emit_event.gno +++ b/gnovm/stdlibs/std/emit_event.gno @@ -1,6 +1,6 @@ package std -// Emit is a function that constructs a gnoEvent with a specified type and attributes. +// Emit is a function that constructs a GnoEvent with a specified type and attributes. // It then forwards this event to the event logger. Each emitted event carries metadata // such as the event type, the initializing realm, and the provided attributes. // diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go index 46fea79d43c..10b00d62cfc 100644 --- a/gnovm/stdlibs/std/emit_event.go +++ b/gnovm/stdlibs/std/emit_event.go @@ -17,27 +17,29 @@ func X_emit(m *gno.Machine, typ string, attrs []string) { m.Panic(typedString(err.Error())) } - pkgPath := CurrentRealmPath(m) + _, pkgPath := currentRealm(m) fnIdent := getPrevFunctionNameFromTarget(m, "Emit") - evt := gnoEvent{ + ctx := GetContext(m) + + evt := GnoEvent{ Type: typ, + Attributes: eventAttrs, PkgPath: pkgPath, Func: fnIdent, - Attributes: eventAttrs, } - ctx := m.Context.(ExecContext) + ctx.EventLogger.EmitEvent(evt) } -func attrKeysAndValues(attrs []string) ([]gnoEventAttribute, error) { +func attrKeysAndValues(attrs []string) ([]GnoEventAttribute, error) { attrLen := len(attrs) if attrLen%2 != 0 { return nil, errInvalidGnoEventAttrs } - eventAttrs := make([]gnoEventAttribute, attrLen/2) + eventAttrs := make([]GnoEventAttribute, attrLen/2) for i := 0; i < attrLen-1; i += 2 { - eventAttrs[i/2] = gnoEventAttribute{ + eventAttrs[i/2] = GnoEventAttribute{ Key: attrs[i], Value: attrs[i+1], } @@ -45,16 +47,16 @@ func attrKeysAndValues(attrs []string) ([]gnoEventAttribute, error) { return eventAttrs, nil } -type gnoEvent struct { +type GnoEvent struct { Type string `json:"type"` + Attributes []GnoEventAttribute `json:"attrs"` PkgPath string `json:"pkg_path"` Func string `json:"func"` - Attributes []gnoEventAttribute `json:"attrs"` } -func (e gnoEvent) AssertABCIEvent() {} +func (e GnoEvent) AssertABCIEvent() {} -type gnoEventAttribute struct { +type GnoEventAttribute struct { Key string `json:"key"` Value string `json:"value"` } diff --git a/gnovm/stdlibs/std/emit_event_test.go b/gnovm/stdlibs/std/emit_event_test.go index 10bd8ecacd9..ecf08eebd60 100644 --- a/gnovm/stdlibs/std/emit_event_test.go +++ b/gnovm/stdlibs/std/emit_event_test.go @@ -13,24 +13,27 @@ import ( func TestEmit(t *testing.T) { m := gno.NewMachine("emit", nil) - pkgPath := CurrentRealmPath(m) + + m.Context = ExecContext{} + + _, pkgPath := X_getRealm(m, 0) tests := []struct { name string eventType string attrs []string - expectedEvents []gnoEvent + expectedEvents []GnoEvent expectPanic bool }{ { name: "SimpleValid", eventType: "test", attrs: []string{"key1", "value1", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "test", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -48,12 +51,12 @@ func TestEmit(t *testing.T) { name: "EmptyAttribute", eventType: "test", attrs: []string{"key1", "", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "test", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: ""}, {Key: "key2", Value: "value2"}, }, @@ -65,12 +68,12 @@ func TestEmit(t *testing.T) { name: "EmptyType", eventType: "", attrs: []string{"key1", "value1", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -82,12 +85,12 @@ func TestEmit(t *testing.T) { name: "EmptyAttributeKey", eventType: "test", attrs: []string{"", "value1", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "test", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -145,12 +148,12 @@ func TestEmit_MultipleEvents(t *testing.T) { t.Fatal(err) } - expect := []gnoEvent{ + expect := []GnoEvent{ { Type: "test1", PkgPath: "", Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -159,7 +162,7 @@ func TestEmit_MultipleEvents(t *testing.T) { Type: "test2", PkgPath: "", Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key3", Value: "value3"}, {Key: "key4", Value: "value4"}, }, @@ -222,7 +225,7 @@ func TestEmit_ContractInteraction(t *testing.T) { t.Fatal(err) } - expected := `[{"type":"foo","pkg_path":"","func":"","attrs":[{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}]},{"type":"qux","pkg_path":"","func":"","attrs":[{"key":"bar","value":"baz"}]}]` + expected := `[{"type":"foo","attrs":[{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}],"pkg_path":"","func":""},{"type":"qux","attrs":[{"key":"bar","value":"baz"}],"pkg_path":"","func":""}]` assert.Equal(t, expected, string(res)) } @@ -250,7 +253,7 @@ func TestEmit_Iteration(t *testing.T) { var builder strings.Builder builder.WriteString("[") for i := 0; i < 10; i++ { - builder.WriteString(`{"type":"bar","pkg_path":"","func":"","attrs":[{"key":"qux","value":"value1"}]},`) + builder.WriteString(`{"type":"bar","attrs":[{"key":"qux","value":"value1"}],"pkg_path":"","func":""},`) } expected := builder.String()[:builder.Len()-1] + "]" @@ -308,7 +311,6 @@ func TestEmit_ComplexInteraction(t *testing.T) { t.Fatal(err) } - expected := `[{"type":"ForLoopEvent","pkg_path":"","func":"","attrs":[{"key":"iteration","value":"0"},{"key":"key","value":"value"}]},{"type":"ForLoopEvent","pkg_path":"","func":"","attrs":[{"key":"iteration","value":"1"},{"key":"key","value":"value"}]},{"type":"ForLoopEvent","pkg_path":"","func":"","attrs":[{"key":"iteration","value":"2"},{"key":"key","value":"value"}]},{"type":"ForLoopCompletionEvent","pkg_path":"","func":"","attrs":[{"key":"count","value":"3"}]},{"type":"CallbackEvent","pkg_path":"","func":"","attrs":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}]},{"type":"CallbackCompletionEvent","pkg_path":"","func":"","attrs":[{"key":"key","value":"value"}]},{"type":"DeferEvent","pkg_path":"","func":"","attrs":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}]}]` - + expected := `[{"type":"ForLoopEvent","attrs":[{"key":"iteration","value":"0"},{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"ForLoopEvent","attrs":[{"key":"iteration","value":"1"},{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"ForLoopEvent","attrs":[{"key":"iteration","value":"2"},{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"ForLoopCompletionEvent","attrs":[{"key":"count","value":"3"}],"pkg_path":"","func":""},{"type":"CallbackEvent","attrs":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}],"pkg_path":"","func":""},{"type":"CallbackCompletionEvent","attrs":[{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"DeferEvent","attrs":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}],"pkg_path":"","func":""}]` assert.Equal(t, expected, string(res)) } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 8043df49882..22a16fc18d1 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,10 +1,17 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func CurrentRealmPath() string // injected -func GetChainID() string // injected -func GetHeight() int64 // injected +// 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 +// is invoked by another method (even from the same realm or package). +// It also returns false every time when the transaction is broadcasted via +// MsgRun. +func IsOriginCall() bool // injected + +func GetChainID() string // injected +func GetHeight() int64 // injected func GetOrigSend() Coins { den, amt := origSend() diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index deb0f1268d2..f185a52f249 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -14,22 +14,21 @@ func AssertOriginCall(m *gno.Machine) { } func IsOriginCall(m *gno.Machine) bool { - return len(m.Frames) == 2 -} - -func CurrentRealmPath(m *gno.Machine) string { - if m.Realm != nil { - return m.Realm.Path + n := m.NumFrames() + if n == 0 { + return false } - return "" + firstPkg := m.Frames[0].LastPackage + isMsgCall := firstPkg != nil && firstPkg.PkgPath == "main" + return n <= 2 && isMsgCall } func GetChainID(m *gno.Machine) string { - return m.Context.(ExecContext).ChainID + return GetContext(m).ChainID } func GetHeight(m *gno.Machine) int64 { - return m.Context.(ExecContext).Height + return GetContext(m).Height } // getPrevFunctionNameFromTarget returns the last called function name (identifier) from the call stack. @@ -60,20 +59,21 @@ func findPrevFuncName(m *gno.Machine, targetIndex int) string { return string(currFunc.Name) } } + panic("function name not found") } func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { - os := m.Context.(ExecContext).OrigSend + os := GetContext(m).OrigSend return ExpandCoins(os) } func X_origCaller(m *gno.Machine) string { - return string(m.Context.(ExecContext).OrigCaller) + return string(GetContext(m).OrigCaller) } func X_origPkgAddr(m *gno.Machine) string { - return string(m.Context.(ExecContext).OrigPkgAddr) + return string(GetContext(m).OrigPkgAddr) } func X_callerAt(m *gno.Machine, n int) string { @@ -92,22 +92,24 @@ func X_callerAt(m *gno.Machine, n int) string { } if n == m.NumFrames() { // This makes it consistent with GetOrigCaller. - ctx := m.Context.(ExecContext) + ctx := GetContext(m) return string(ctx.OrigCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { +func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) { + // NOTE: keep in sync with test/stdlibs/std.getRealm + var ( - ctx = m.Context.(ExecContext) + ctx = GetContext(m) currentCaller crypto.Bech32Address // Keeps track of the number of times currentCaller // has changed. changes int ) - for i := m.NumFrames() - 1; i > 0; i-- { + for i := m.NumFrames() - 1; i >= 0; i-- { fr := m.Frames[i] if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { continue @@ -130,6 +132,12 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { return string(ctx.OrigCaller), "" } +// currentRealm retrieves the current realm's address and pkgPath. +// It's not a native binding; but is used within this package to clarify usage. +func currentRealm(m *gno.Machine) (address, pkgPath string) { + return X_getRealm(m, 0) +} + func X_derivePkgAddr(pkgPath string) string { return string(gno.DerivePkgAddr(pkgPath).Bech32()) } diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go new file mode 100644 index 00000000000..851785575d7 --- /dev/null +++ b/gnovm/stdlibs/std/native_test.go @@ -0,0 +1,194 @@ +package std + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/tm2/pkg/crypto" +) + +func TestPrevRealmIsOrigin(t *testing.T) { + var ( + user = gno.DerivePkgAddr("user1.gno").Bech32() + ctx = ExecContext{ + OrigCaller: user, + } + msgCallFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "main"}} + msgRunFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/g1337/run"}} + ) + tests := []struct { + name string + machine *gno.Machine + expectedAddr crypto.Bech32Address + expectedPkgPath string + expectedIsOriginCall bool + }{ + { + name: "no frames", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{}, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "one frame w/o LastPackage", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + {LastPackage: nil}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "one package frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "one realm frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "one msgCall frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + msgCallFrame, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: true, + }, + { + name: "one msgRun frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + msgRunFrame, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "one package frame and one msgCall frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + msgCallFrame, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: true, + }, + { + name: "one realm frame and one msgCall frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + msgCallFrame, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: true, + }, + { + name: "one package frame and one msgRun frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + msgRunFrame, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "one realm frame and one msgRun frame", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + msgRunFrame, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "multiple frames with one realm", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/xxx"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/xxx"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/xxx"}}, + }, + }, + expectedAddr: user, + expectedPkgPath: "", + expectedIsOriginCall: false, + }, + { + name: "multiple frames with multiple realms", + machine: &gno.Machine{ + Context: ctx, + Frames: []*gno.Frame{ + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/zzz"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/zzz"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/yyy"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/yyy"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/p/xxx"}}, + {LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/xxx"}}, + }, + }, + expectedAddr: gno.DerivePkgAddr("gno.land/r/yyy").Bech32(), + expectedPkgPath: "gno.land/r/yyy", + expectedIsOriginCall: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + addr, pkgPath := X_getRealm(tt.machine, 1) + isOrigin := IsOriginCall(tt.machine) + + assert.Equal(string(tt.expectedAddr), addr) + assert.Equal(tt.expectedPkgPath, pkgPath) + assert.Equal(tt.expectedIsOriginCall, isOrigin) + }) + } +} diff --git a/gnovm/stdlibs/std/package.go b/gnovm/stdlibs/std/package.go index 219f196b2d9..05d30cb38da 100644 --- a/gnovm/stdlibs/std/package.go +++ b/gnovm/stdlibs/std/package.go @@ -14,6 +14,6 @@ var Package = amino.RegisterPackage(amino.NewPackage( abci.Package, ). WithTypes( - gnoEventAttribute{}, - gnoEvent{}, + GnoEventAttribute{}, + GnoEvent{}, )) diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 7939c0396d1..c9b16815ab5 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -9,11 +9,28 @@ import ( type ExecContext = libsstd.ExecContext -func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { - for _, nf := range nativeFuncs { +func GetContext(m *gno.Machine) ExecContext { + return libsstd.GetContext(m) +} + +// FindNative returns the NativeFunc associated with the given pkgPath+name +// combination. If there is none, FindNative returns nil. +func FindNative(pkgPath string, name gno.Name) *NativeFunc { + for i, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { - return nf.f + return &nativeFuncs[i] } } return nil } + +// NativeStore is used by the GnoVM to determine if the given function, +// specified by its pkgPath and name, has a native implementation; and if so +// retrieve it. +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + nt := FindNative(pkgPath, name) + if nt == nil { + return nil + } + return nt.f +} diff --git a/gnovm/stdlibs/stdshim/addr_set.gno b/gnovm/stdlibs/stdshim/addr_set.gno deleted file mode 100644 index 3f46f48b8b5..00000000000 --- a/gnovm/stdlibs/stdshim/addr_set.gno +++ /dev/null @@ -1,47 +0,0 @@ -package std - -import "errors" - -//---------------------------------------- -// AddressSet - -type AddressSet interface { - Size() int - AddAddress(Address) error - HasAddress(Address) bool -} - -//---------------------------------------- -// AddressList implements AddressSet. -// TODO implement AddressTree with avl. - -type AddressList []Address - -func NewAddressList() *AddressList { - return &AddressList{} -} - -func (alist *AddressList) Size() int { - return len(*alist) -} - -func (alist *AddressList) AddAddress(newAddr Address) error { - // TODO optimize with binary algorithm - for _, addr := range *alist { - if addr == newAddr { - return errors.New("address already exists") - } - } - *alist = append(*alist, newAddr) - return nil -} - -func (alist *AddressList) HasAddress(newAddr Address) bool { - // TODO optimize with binary algorithm - for _, addr := range *alist { - if addr == newAddr { - return true - } - } - return false -} diff --git a/gnovm/stdlibs/stdshim/banker.gno b/gnovm/stdlibs/stdshim/banker.gno deleted file mode 100644 index 44e611b780f..00000000000 --- a/gnovm/stdlibs/stdshim/banker.gno +++ /dev/null @@ -1,70 +0,0 @@ -package std - -// Realm functions can call std.GetBanker(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 -// functions; this allows other realms to spend coins on -// behalf of the first realm. -// -// Banker panics on errors instead of returning errors. -// This also helps simplify the interface and prevent -// hidden bugs (e.g. ignoring errors) -// -// NOTE: this Gno interface is satisfied by a native go -// type, and those can't return non-primitive objects -// (without confusion). -type Banker interface { - GetCoins(addr Address) (dst Coins) - SendCoins(from, to Address, amt Coins) - TotalCoin(denom string) int64 - IssueCoin(addr Address, denom string, amount int64) - RemoveCoin(addr Address, denom string, amount int64) -} - -// Also available natively in stdlibs/context.go -type BankerType uint8 - -// Also available natively in stdlibs/context.go -const ( - // Can only read state. - BankerTypeReadonly BankerType = iota - // Can only send from tx send. - BankerTypeOrigSend - // Can send from all realm coins. - BankerTypeRealmSend - // Can issue and remove realm coins. - BankerTypeRealmIssue -) - -//---------------------------------------- -// adapter for native banker - -type bankAdapter struct { - nativeBanker Banker -} - -func (ba bankAdapter) GetCoins(addr Address) (dst Coins) { - // convert native -> gno - coins := ba.nativeBanker.GetCoins(addr) - for _, coin := range coins { - dst = append(dst, (Coin)(coin)) - } - return dst -} - -func (ba bankAdapter) SendCoins(from, to Address, amt Coins) { - ba.nativeBanker.SendCoins(from, to, amt) -} - -func (ba bankAdapter) TotalCoin(denom string) int64 { - return ba.nativeBanker.TotalCoin(denom) -} - -func (ba bankAdapter) IssueCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.IssueCoin(addr, denom, amount) -} - -func (ba bankAdapter) RemoveCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.RemoveCoin(addr, denom, amount) -} diff --git a/gnovm/stdlibs/stdshim/coins.gno b/gnovm/stdlibs/stdshim/coins.gno deleted file mode 100644 index aaede81309d..00000000000 --- a/gnovm/stdlibs/stdshim/coins.gno +++ /dev/null @@ -1,65 +0,0 @@ -package std - -import "strconv" - -// NOTE: this is selectly copied over from pkgs/std/coin.go -// TODO: import all functionality(?). - -// Coin hold some amount of one currency. -// A negative amount is invalid. -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - -func (c Coin) String() string { - return strconv.Itoa(int(c.Amount)) + c.Denom -} - -func (c Coin) IsGTE(other Coin) bool { - if c.Denom != other.Denom { - panic("invalid coin denominations: " + c.Denom) - } - return c.Amount >= other.Amount -} - -// Coins is a set of Coin, one per currency -type Coins []Coin - -func (cz Coins) String() string { - res := "" - for i, c := range cz { - if i > 0 { - res += "," - } - res += c.String() - } - return res -} - -func (cz Coins) AmountOf(denom string) int64 { - for _, c := range cz { - if c.Denom == denom { - return c.Amount - } - } - return 0 -} - -func (a Coins) Add(b Coins) Coins { - c := Coins{} - for _, ac := range a { - bc := b.AmountOf(ac.Denom) - ac.Amount += bc - c = append(c, ac) - } - for _, bc := range b { - cc := c.AmountOf(bc.Denom) - if cc == 0 { - c = append(c, bc) - } - } - return c -} - -// TODO implement Coin/Coins constructors. diff --git a/gnovm/stdlibs/stdshim/context.gno b/gnovm/stdlibs/stdshim/context.gno deleted file mode 100644 index 878c963b22b..00000000000 --- a/gnovm/stdlibs/stdshim/context.gno +++ /dev/null @@ -1,4 +0,0 @@ -package std - -// ExecContext is not exposed, -// use native injections std.GetChainID(), std.GetHeight() etc instead. diff --git a/gnovm/stdlibs/stdshim/crypto.gno b/gnovm/stdlibs/stdshim/crypto.gno deleted file mode 100644 index 402a6af3e22..00000000000 --- a/gnovm/stdlibs/stdshim/crypto.gno +++ /dev/null @@ -1,17 +0,0 @@ -package std - -type Address string // NOTE: bech32 - -func (a Address) String() string { - return string(a) -} - -// IsValid checks if the address is valid bech32 encoded string -func (a Address) IsValid() bool { - _, _, ok := DecodeBech32(a) - return ok -} - -const RawAddressSize = 20 - -type RawAddress [RawAddressSize]byte diff --git a/gnovm/stdlibs/stdshim/frame.gno b/gnovm/stdlibs/stdshim/frame.gno deleted file mode 100644 index bc3a000f5a0..00000000000 --- a/gnovm/stdlibs/stdshim/frame.gno +++ /dev/null @@ -1,18 +0,0 @@ -package std - -type Realm struct { - addr Address - pkgPath string -} - -func (r Realm) Addr() Address { - return r.addr -} - -func (r Realm) PkgPath() string { - return r.pkgPath -} - -func (r Realm) IsUser() bool { - return r.pkgPath == "" -} diff --git a/gnovm/stdlibs/stdshim/stdshim.gno b/gnovm/stdlibs/stdshim/stdshim.gno deleted file mode 100644 index 62e97088209..00000000000 --- a/gnovm/stdlibs/stdshim/stdshim.gno +++ /dev/null @@ -1,86 +0,0 @@ -package std - -const shimWarn = "stdshim cannot be used to run code" - -func AssertOriginCall() { - panic(shimWarn) -} - -func IsOriginCall() (isOrigin bool) { - panic(shimWarn) - return false -} - -func Hash(bz []byte) (hash [20]byte) { - panic(shimWarn) - return -} - -func CurrentRealmPath() string { - panic(shimWarn) - return "" -} - -func GetChainID() string { - panic(shimWarn) - return "" -} - -func GetHeight() int64 { - panic(shimWarn) - return -1 -} - -func GetOrigSend() Coins { - panic(shimWarn) - return Coins{} -} - -func CurrentRealm() Realm { - panic(shimWarn) - return Realm{ - addr: Address(""), - pkgPath: "", - } -} - -func PrevRealm() Realm { - panic(shimWarn) - return Realm{ - addr: Address(""), - pkgPath: "", - } -} - -func GetOrigCaller() Address { - panic(shimWarn) - return Address("") -} - -func GetOrigPkgAddr() Address { - panic(shimWarn) - return Address("") -} - -func GetCallerAt(n int) Address { - panic(shimWarn) - return Address("") -} - -func GetBanker(bankerType BankerType) Banker { - panic(shimWarn) - return nil -} - -func EncodeBech32(prefix string, bytes [20]byte) (addr Address) { - panic(shimWarn) - return "" -} - -func DecodeBech32(addr Address) (prefix string, bytes [20]byte, ok bool) { - panic(shimWarn) -} - -func DerivePkgAddr(pkgPath string) (addr Address) { - panic(shimWarn) -} diff --git a/gnovm/stdlibs/testing/fuzz.gno b/gnovm/stdlibs/testing/fuzz.gno index 62a36800b2b..74d64c231b6 100644 --- a/gnovm/stdlibs/testing/fuzz.gno +++ b/gnovm/stdlibs/testing/fuzz.gno @@ -1,10 +1,6 @@ package testing -import ( - "math" - "strings" - "time" -) +import "strings" type Fuzzer interface { InsertDeleteMutate(p float64) Fuzzer @@ -242,10 +238,7 @@ func (f *F) evolve(generations int) { newPopulation = append(newPopulation, population[i]) } - var ( - bestFitness int - bestIndividual string - ) + var bestFitness int for _, ind := range newPopulation { if GenerateRandomBool(0.2) { @@ -260,7 +253,6 @@ func (f *F) evolve(generations int) { if ind.Fitness > bestFitness { bestFitness = ind.Fitness - bestIndividual = ind.Fuzzer.String() } } diff --git a/gnovm/stdlibs/testing/fuzz_test.gno b/gnovm/stdlibs/testing/fuzz_test.gno index 454c1cccd6e..307148c09b3 100644 --- a/gnovm/stdlibs/testing/fuzz_test.gno +++ b/gnovm/stdlibs/testing/fuzz_test.gno @@ -1,10 +1,6 @@ package testing -import ( - "encoding/binary" - "strings" - "time" -) +import "strings" func TestMutate(t *T) { originalValue := "Hello" diff --git a/gnovm/stdlibs/testing/match.gno b/gnovm/stdlibs/testing/match.gno index 5b7f509d8f6..3b22602890d 100644 --- a/gnovm/stdlibs/testing/match.gno +++ b/gnovm/stdlibs/testing/match.gno @@ -9,7 +9,6 @@ package testing import ( "fmt" "regexp" - "strconv" "strings" "unicode" ) diff --git a/gnovm/stdlibs/testing/random.gno b/gnovm/stdlibs/testing/random.gno index b7ee6ca93ef..9d55abb6e47 100644 --- a/gnovm/stdlibs/testing/random.gno +++ b/gnovm/stdlibs/testing/random.gno @@ -1,9 +1,6 @@ package testing -import ( - "math" - "time" -) +import "math" // Internal state for the random number generator. var x uint64 = 42 diff --git a/gnovm/stdlibs/testing/random_test.gno b/gnovm/stdlibs/testing/random_test.gno index 8c1c741b2b8..7fcc00552ea 100644 --- a/gnovm/stdlibs/testing/random_test.gno +++ b/gnovm/stdlibs/testing/random_test.gno @@ -2,7 +2,6 @@ package testing import ( "math" - "strconv" "time" ) diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index fb13c0f39cd..6e55c5cc283 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -5,12 +5,11 @@ import ( "encoding/json" "fmt" "os" - "regexp" "strconv" "strings" ) -//---------------------------------------- +// ---------------------------------------- // Top level functions // skipErr is the type of the panic created by SkipNow @@ -61,7 +60,7 @@ func AllocsPerRun2(runs int, f func()) (total int) { return 0 } -//---------------------------------------- +// ---------------------------------------- // T type T struct { @@ -226,7 +225,7 @@ func (t *T) report() Report { } } -//---------------------------------------- +// ---------------------------------------- // B // TODO: actually implement @@ -262,7 +261,7 @@ func (b *B) StartTimer() { panic("not yet implemen func (b *B) StopTimer() { panic("not yet implemented") } func (b *B) TempDir() string { panic("not yet implemented") } -//---------------------------------------- +// ---------------------------------------- // PB // TODO: actually implement diff --git a/gnovm/stdlibs/time/format.gno b/gnovm/stdlibs/time/format.gno index 8431ff89b45..61a9eb3301b 100644 --- a/gnovm/stdlibs/time/format.gno +++ b/gnovm/stdlibs/time/format.gno @@ -116,6 +116,9 @@ const ( StampMilli = "Jan _2 15:04:05.000" StampMicro = "Jan _2 15:04:05.000000" StampNano = "Jan _2 15:04:05.000000000" + DateTime = "2006-01-02 15:04:05" + DateOnly = "2006-01-02" + TimeOnly = "15:04:05" ) const ( diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 521679e48d5..f3395142d1d 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -80,7 +80,6 @@ package time import ( "errors" - // XXX _ "unsafe" // for go:linkname ) // A Time represents an instant in time with nanosecond precision. @@ -1072,8 +1071,6 @@ func daysSinceEpoch(year int) uint64 { func now() (sec int64, nsec int32, mono int64) // injected // runtimeNano returns the current value of the runtime clock in nanoseconds. -// -//go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 { _, _, mono := now() return mono diff --git a/gnovm/stdlibs/time/time.go b/gnovm/stdlibs/time/time.go index 8c1c768715e..9ecd8a3b302 100644 --- a/gnovm/stdlibs/time/time.go +++ b/gnovm/stdlibs/time/time.go @@ -12,6 +12,6 @@ func X_now(m *gno.Machine) (sec int64, nsec int32, mono int64) { return 0, 0, 0 } - ctx := m.Context.(std.ExecContext) + ctx := std.GetContext(m) return ctx.Timestamp, int32(ctx.TimestampNano), ctx.Timestamp*int64(time.Second) + ctx.TimestampNano } diff --git a/gnovm/stdlibs/unicode/utf16/utf16_test.gno b/gnovm/stdlibs/unicode/utf16/utf16_test.gno index b56fd81494d..11ff921c3dc 100644 --- a/gnovm/stdlibs/unicode/utf16/utf16_test.gno +++ b/gnovm/stdlibs/unicode/utf16/utf16_test.gno @@ -7,7 +7,6 @@ package utf16 import ( "testing" "unicode" - "unicode/utf16" ) type encodeTest struct { diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 8db662e53fb..8ab60145bd5 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -15,6 +15,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" + teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/sdk" @@ -35,7 +36,7 @@ func TestMachine(store gno.Store, stdout io.Writer, pkgPath string) *gno.Machine } func testMachineCustom(store gno.Store, pkgPath string, stdout io.Writer, maxAlloc int64, send std.Coins) *gno.Machine { - ctx := testContext(pkgPath, send) + ctx := TestContext(pkgPath, send) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "", // set later. Output: stdout, @@ -46,7 +47,8 @@ func testMachineCustom(store gno.Store, pkgPath string, stdout io.Writer, maxAll return m } -func testContext(pkgPath string, send std.Coins) stdlibs.ExecContext { +// TestContext returns a TestExecContext. Usable for test purpose only. +func TestContext(pkgPath string, send std.Coins) *teststd.TestExecContext { // FIXME: create a better package to manage this, with custom constructors pkgAddr := gno.DerivePkgAddr(pkgPath) // the addr of the pkgPath called. caller := gno.DerivePkgAddr("user1.gno") @@ -65,7 +67,10 @@ func testContext(pkgPath string, send std.Coins) stdlibs.ExecContext { Banker: banker, EventLogger: sdk.NewEventLogger(), } - return ctx + return &teststd.TestExecContext{ + ExecContext: ctx, + RealmFrames: make(map[*gno.Frame]teststd.RealmOverride), + } } type runFileTestOptions struct { @@ -136,14 +141,13 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { defer func() { if r := recover(); r != nil { // print output. - fmt.Println("OUTPUT:\n", stdout.String()) - // print stack if unexpected error. + fmt.Printf("OUTPUT:\n%s\n", stdout.String()) pnc = r - if errWanted == "" { - rtdb.PrintStack() - } err := strings.TrimSpace(fmt.Sprintf("%v", pnc)) - if !strings.Contains(err, errWanted) { + // print stack if unexpected error. + if errWanted == "" || + !strings.Contains(err, errWanted) { + fmt.Printf("ERROR:\n%s\n", err) // error didn't match: print stack // NOTE: will fail testcase later. rtdb.PrintStack() @@ -258,8 +262,19 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { default: errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) } + + parts := strings.SplitN(errstr, ":\n--- preprocess stack ---", 2) + if len(parts) == 2 { + fmt.Println(parts[0]) + errstr = parts[0] + } if errstr != errWanted { - panic(fmt.Sprintf("fail on %s: got %q, want: %q", path, errstr, errWanted)) + if f.syncWanted { + // write error to file + replaceWantedInPlace(path, "Error", errstr) + } else { + panic(fmt.Sprintf("fail on %s: got %q, want: %q", path, errstr, errWanted)) + } } // NOTE: ignores any gno.GetDebugErrors(). @@ -274,12 +289,16 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { } else { errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) } + parts := strings.SplitN(errstr, ":\n--- preprocess stack ---", 2) + if len(parts) == 2 { + fmt.Println(parts[0]) + errstr = parts[0] + } // check tip line, write to file - ctl := fmt.Sprintf( - errstr + - "\n*** CHECK THE ERR MESSAGES ABOVE, MAKE SURE IT'S WHAT YOU EXPECTED, " + - "DELETE THIS LINE AND RUN TEST AGAIN ***", - ) + ctl := errstr + + "\n*** CHECK THE ERR MESSAGES ABOVE, MAKE SURE IT'S WHAT YOU EXPECTED, " + + "DELETE THIS LINE AND RUN TEST AGAIN ***" + // write error to file replaceWantedInPlace(path, "Error", ctl) panic(fmt.Sprintf("fail on %s: err recorded, check the message and run test again", path)) } diff --git a/gnovm/tests/file_test.go b/gnovm/tests/file_test.go index 5622892efa1..4313fd88645 100644 --- a/gnovm/tests/file_test.go +++ b/gnovm/tests/file_test.go @@ -33,35 +33,14 @@ func TestFiles(t *testing.T) { } func TestChallenges(t *testing.T) { + t.Skip("Challenge tests, skipping.") baseDir := filepath.Join(".", "challenges") runFileTests(t, baseDir, nil) } -func filterFileTests(t *testing.T, files []fs.DirEntry, ignore []string) []fs.DirEntry { - t.Helper() - - for i := 0; i < len(files); i++ { - file := files[i] - skip := func() { files = append(files[:i], files[i+1:]...); i-- } - if filepath.Ext(file.Name()) != ".gno" { - skip() - continue - } - for _, is := range ignore { - if match, err := path.Match(is, file.Name()); match { - skip() - continue - } else if err != nil { - t.Fatalf("error parsing glob pattern %q: %v", is, err) - } - } - if testing.Short() && strings.Contains(file.Name(), "_long") { - t.Logf("skipping test %s in short mode.", file.Name()) - skip() - continue - } - } - return files +type testFile struct { + path string + fs.DirEntry } // ignore are glob patterns to ignore @@ -70,34 +49,84 @@ func runFileTests(t *testing.T, baseDir string, ignore []string, opts ...RunFile opts = append([]RunFileTestOption{WithSyncWanted(*withSync)}, opts...) - files, err := os.ReadDir(baseDir) + files, err := readFiles(t, baseDir) if err != nil { t.Fatal(err) } files = filterFileTests(t, files, ignore) - + var path string + var name string for _, file := range files { - file := file - t.Run(file.Name(), func(t *testing.T) { - if file.Name() == "composite0b.gno" { - t.Skip("challenge failing") - } - if file.Name() == "composite1b.gno" { - t.Skip("challenge failing") - } - if file.Name() == "not_a_type.gno" { - t.Skip("challenge failing") - } - if file.Name() == "unused0.gno" { - t.Skip("challenge failing") - } - - runFileTest(t, filepath.Join(baseDir, file.Name()), opts...) + path = file.path + name = strings.TrimPrefix(file.path, baseDir+string(os.PathSeparator)) + t.Run(name, func(t *testing.T) { + runFileTest(t, path, opts...) }) } } +// it reads all files recursively in the directory +func readFiles(t *testing.T, dir string) ([]testFile, error) { + t.Helper() + var files []testFile + + err := filepath.WalkDir(dir, func(path string, de fs.DirEntry, err error) error { + if err != nil { + return err + } + if de.IsDir() && de.Name() == "extern" { + return filepath.SkipDir + } + f := testFile{path: path, DirEntry: de} + + files = append(files, f) + return nil + }) + return files, err +} + +func filterFileTests(t *testing.T, files []testFile, ignore []string) []testFile { + t.Helper() + filtered := make([]testFile, 0, 1000) + var name string + + for _, f := range files { + // skip none .gno files + name = f.DirEntry.Name() + if filepath.Ext(name) != ".gno" { + continue + } + // skip ignored files + if isIgnored(t, name, ignore) { + continue + } + // skip _long file if we only want to test regular file. + if testing.Short() && strings.Contains(name, "_long") { + t.Logf("skipping test %s in short mode.", name) + continue + } + filtered = append(filtered, f) + } + return filtered +} + +func isIgnored(t *testing.T, name string, ignore []string) bool { + t.Helper() + isIgnore := false + for _, is := range ignore { + match, err := path.Match(is, name) + if err != nil { + t.Fatalf("error parsing glob pattern %q: %v", is, err) + } + if match { + isIgnore = true + break + } + } + return isIgnore +} + func runFileTest(t *testing.T, path string, opts ...RunFileTestOption) { t.Helper() diff --git a/gnovm/tests/files/access0.gno b/gnovm/tests/files/access0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access0.gno rename to gnovm/tests/files/access0_stdlibs.gno diff --git a/gnovm/tests/files/access1.gno b/gnovm/tests/files/access1_stdlibs.gno similarity index 52% rename from gnovm/tests/files/access1.gno rename to gnovm/tests/files/access1_stdlibs.gno index d9bb9f46203..5a1bf4cc12e 100644 --- a/gnovm/tests/files/access1.gno +++ b/gnovm/tests/files/access1_stdlibs.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/access1.gno:8: cannot access gno.land/p/demo/testutils.testVar2 from main +// main/files/access1_stdlibs.gno:8:10: cannot access gno.land/p/demo/testutils.testVar2 from main diff --git a/gnovm/tests/files/access2.gno b/gnovm/tests/files/access2_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access2.gno rename to gnovm/tests/files/access2_stdlibs.gno diff --git a/gnovm/tests/files/access3.gno b/gnovm/tests/files/access3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access3.gno rename to gnovm/tests/files/access3_stdlibs.gno diff --git a/gnovm/tests/files/access4.gno b/gnovm/tests/files/access4_stdlibs.gno similarity index 56% rename from gnovm/tests/files/access4.gno rename to gnovm/tests/files/access4_stdlibs.gno index bb2dcc340f0..e38a6d2ea4a 100644 --- a/gnovm/tests/files/access4.gno +++ b/gnovm/tests/files/access4_stdlibs.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/access4.gno:9: cannot access gno.land/p/demo/testutils.TestAccessStruct.privateField from main +// main/files/access4_stdlibs.gno:9:10: cannot access gno.land/p/demo/testutils.TestAccessStruct.privateField from main diff --git a/gnovm/tests/files/access5.gno b/gnovm/tests/files/access5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access5.gno rename to gnovm/tests/files/access5_stdlibs.gno diff --git a/gnovm/tests/files/access6.gno b/gnovm/tests/files/access6_stdlibs.gno similarity index 61% rename from gnovm/tests/files/access6.gno rename to gnovm/tests/files/access6_stdlibs.gno index 991b4a5457a..443f2f5291d 100644 --- a/gnovm/tests/files/access6.gno +++ b/gnovm/tests/files/access6_stdlibs.gno @@ -16,4 +16,4 @@ func main() { } // Error: -// main/files/access6.gno:15: main.mystruct does not implement gno.land/p/demo/testutils.PrivateInterface +// main/files/access6_stdlibs.gno:15:2: main.mystruct does not implement gno.land/p/demo/testutils.PrivateInterface (missing method privateMethod) diff --git a/gnovm/tests/files/access7.gno b/gnovm/tests/files/access7_stdlibs.gno similarity index 67% rename from gnovm/tests/files/access7.gno rename to gnovm/tests/files/access7_stdlibs.gno index 2b4c32ab818..01c9ed83fa0 100644 --- a/gnovm/tests/files/access7.gno +++ b/gnovm/tests/files/access7_stdlibs.gno @@ -20,4 +20,4 @@ func main() { } // Error: -// main/files/access7.gno:19: main.PrivateInterface2 does not implement gno.land/p/demo/testutils.PrivateInterface +// main/files/access7_stdlibs.gno:19:2: main.PrivateInterface2 does not implement gno.land/p/demo/testutils.PrivateInterface (missing method privateMethod) diff --git a/gnovm/tests/files/assign11.gno b/gnovm/tests/files/assign11.gno index 4eb17854b94..76284a75e0b 100644 --- a/gnovm/tests/files/assign11.gno +++ b/gnovm/tests/files/assign11.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/assign11.gno:6: assignment mismatch: 3 variables but fmt.Println returns 2 values +// main/files/assign11.gno:6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values diff --git a/gnovm/tests/files/assign12.gno b/gnovm/tests/files/assign12.gno index 2fdfc29b787..5e62fd9c01a 100644 --- a/gnovm/tests/files/assign12.gno +++ b/gnovm/tests/files/assign12.gno @@ -8,4 +8,4 @@ func main() { } // Error: -// main/files/assign12.gno:6: assignment mismatch: 3 variables but fmt.Println returns 2 values +// main/files/assign12.gno:6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values diff --git a/gnovm/tests/files/assign22.gno b/gnovm/tests/files/assign22.gno index bedb8b4adb8..4234a526983 100644 --- a/gnovm/tests/files/assign22.gno +++ b/gnovm/tests/files/assign22.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/assign22.gno:7: assignment operator <<= requires only one expression on lhs and rhs +// main/files/assign22.gno:7:2: assignment operator <<= requires only one expression on lhs and rhs diff --git a/gnovm/tests/files/assign23.gno b/gnovm/tests/files/assign23.gno index ab648774028..2f36ddf866f 100644 --- a/gnovm/tests/files/assign23.gno +++ b/gnovm/tests/files/assign23.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/assign23.gno:7: assignment operator += requires only one expression on lhs and rhs +// main/files/assign23.gno:7:2: assignment operator += requires only one expression on lhs and rhs diff --git a/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type2_filetest.gno new file mode 100644 index 00000000000..bbd9e00919f --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type2_filetest.gno @@ -0,0 +1,16 @@ +package main + +type ( + nat []int + nat64 []int64 +) + +func main() { + var nlist = nat{0} + var n64list = nat64{1} + a := append(n64list, nlist...) + println(a) +} + +// Error: +// main/files/assign_unnamed_type/append/named_unnamed_type2_filetest.gno:11:7: cannot use int as int64 diff --git a/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type3_filetest.gno b/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type3_filetest.gno new file mode 100644 index 00000000000..912aedb468d --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type3_filetest.gno @@ -0,0 +1,15 @@ +package main + +type nat []int + +func main() { + var nlist = nat{0} + var ulist = []int{1} + + a := append(nlist, nlist[0], ulist[0]) + println(a) + +} + +// Output: +// (slice[(0 int),(0 int),(1 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type_filetest.gno b/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type_filetest.gno new file mode 100644 index 00000000000..7c37919f514 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/append/named_unnamed_type_filetest.gno @@ -0,0 +1,35 @@ +package main + +type nat []int + +func main() { + var nlist = nat{0} + var ulist = []int{1} + var n int = 3 + + a := append(nlist, n) + b := append(ulist, n) + + println(a) + println(b) + + a = append(ulist, n) + b = append(nlist, n) + + c := append(nlist, ulist...) + d := append(ulist, nlist...) + + println(a) + println(b) + println(c) + println(d) + +} + +// Output: +// (slice[(0 int),(3 int)] main.nat) +// slice[(1 int),(3 int)] +// (slice[(1 int),(3 int)] main.nat) +// slice[(0 int),(3 int)] +// (slice[(0 int),(1 int)] main.nat) +// slice[(1 int),(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/copy/name_unnamed_type_filetest.gno b/gnovm/tests/files/assign_unnamed_type/copy/name_unnamed_type_filetest.gno new file mode 100644 index 00000000000..0351f4f62db --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/copy/name_unnamed_type_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat []int + +func main() { + var nlist = nat{0} + var ulist = []int{1} + + copy(nlist, ulist) + + println(nlist) + println(ulist) + + nlist = nat{0} + ulist = []int{1} + copy(ulist, nlist) + println(nlist) + println(ulist) +} + +// Output: +// (slice[(1 int)] main.nat) +// slice[(1 int)] +// (slice[(0 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno new file mode 100644 index 00000000000..0e999ca130f --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno @@ -0,0 +1,26 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} + +func main() { + var u1 []int + var n2 nat + + _, n2 = x() + // .tmp1, .tmp_2 := x() + // _, u2 = .tmp1, .tmp_2 + + println(u1) + println(n2) + +} + +// Output: +// (nil []int) +// (slice[(2 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno new file mode 100644 index 00000000000..9fed8f29cc6 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno @@ -0,0 +1,26 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} + +func main() { + var u1 []int + var n2 nat + + u1, _ = x() + // .tmp1, .tmp_2 := x() + // u1, _ = .tmp1, .tmp_2 + + println(u1) + println(n2) + +} + +// Output: +// slice[(1 int)] +// (nil main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno new file mode 100644 index 00000000000..db8a0838c7a --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno @@ -0,0 +1,30 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} + +func main() { + var u1 []int + var n2 nat + // BlockStmt + { + u1, n2 = x() + // .tmp0_1, .tmp0_2 := x() + // u1, n2 = .tmp0_1, .tmp0_2 + println(u1) + println(n2) + println(u1) + println(n2) + } +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno new file mode 100644 index 00000000000..6d8f05807c7 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno @@ -0,0 +1,48 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + var u1 []int + var n2 nat + // if block + if true { + u1, n2 = x() + // .tmp_1, .tmp_2 := x() + // u1, n2 = .tmp_1, .tmp_2 + println(u1) + println(n2) + println(u1) + println(n2) + } + // else block + if false { + + } else { + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + } +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno new file mode 100644 index 00000000000..cae371f3821 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno @@ -0,0 +1,69 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + var u1 []int + var n2 nat + + // if, for, range block + + if true { + u1, n2 = x() + println(u1) + println(n2) + u1, n2 = y() + println(u1) + println(n2) + + for i := 0; i < 2; i++ { + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + } + + zeros := []int{0, 0} + for _, _ = range zeros { + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + } + + } +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno new file mode 100644 index 00000000000..4905b3d58de --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno @@ -0,0 +1,71 @@ +package main + +type nat []int + +// gloabal variables +var u1 []int +var n2 nat + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + + // else-if, for block + if false { + + } else if true { + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + + for i := 0; i < 2; i++ { + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + } + } +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno new file mode 100644 index 00000000000..d73e908f6c6 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno @@ -0,0 +1,52 @@ +package main + +type nat []int + +// gloabal variables +var u1 []int +var n2 nat + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + // function block + fn() +} + +func fn() { + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + + for i := 0; i < 2; i++ { + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + } +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno new file mode 100644 index 00000000000..ea8e042d18b --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno @@ -0,0 +1,46 @@ +package main + +type nat []int + +// gloabal variables +var u1 []int +var n2 nat + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} + +func main() { + fn() +} + +func fn() { + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + + // function literal block + u1, n2 = func() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b + }() + println(u1) + println(n2) + println(u1) + println(n2) +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno new file mode 100644 index 00000000000..05201a6bd2b --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno @@ -0,0 +1,49 @@ +package main + +type nat []int + +// gloabal variables +var u1 []int +var n2 nat + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + fn() +} + +func fn() { + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + + // function literal block + func() { + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + + }() +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno new file mode 100644 index 00000000000..c8f9badc380 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno @@ -0,0 +1,63 @@ +package main + +type nat []int + +// gloabal variables +var u1 []int +var n2 nat + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + fn() +} + +func fn() { + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + // for block + // switch case block + + for i := 0; i < 2; i++ { + switch i { + case 0: + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + default: + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + + } + } +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno new file mode 100644 index 00000000000..c8749cd862a --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno @@ -0,0 +1,50 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + var u1 []int + var n2 nat + + // multiple statements + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + u1, n2 = y() + println(u1) + println(n2) + println(u1) + println(n2) + u1, n2 = x() + println(u1) + println(n2) + println(u1) + println(n2) + +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(4 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) +// slice[(1 int)] +// (slice[(2 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno new file mode 100644 index 00000000000..06617a90ba6 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno @@ -0,0 +1,40 @@ +package main + +type nat []int + +// package block +var n1, u2 = x() + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} +func y() (nat, []int) { + a := nat{3} + b := []int{4} + return a, b +} +func main() { + + // multiple statements + println(n1) + println(u2) + + u2, n1 = y() + println(n1) + println(u2) + + n1, u2 = x() + println(n1) + println(u2) + +} + +// Output: +// (slice[(1 int)] main.nat) +// slice[(2 int)] +// (slice[(4 int)] main.nat) +// slice[(3 int)] +// (slice[(1 int)] main.nat) +// slice[(2 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno new file mode 100644 index 00000000000..e84fdf99567 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno @@ -0,0 +1,19 @@ +package main + +type nat []int + +// package block +var n1, n2 nat = x() + +func x() (nat, []int) { + return nat{1}, nat{2} +} + +func main() { + println(n1) + println(n2) +} + +// Output: +// (slice[(1 int)] main.nat) +// (slice[(2 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno new file mode 100644 index 00000000000..863649f3705 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno @@ -0,0 +1,19 @@ +package main + +type nat []int + +// package block +var u1, u2 []int = x() + +func x() (nat, []int) { + return []int{1}, nat{2} +} + +func main() { + println(u1) + println(u2) +} + +// Output: +// slice[(1 int)] +// slice[(2 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno new file mode 100644 index 00000000000..44f9992a269 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno @@ -0,0 +1,26 @@ +package main + +type nat []int + +func x() (nat, []int) { + a := nat{1} + b := []int{2} + return a, b +} + +func main() { + var u1 []int + var n2 nat + + u1, n2 = x() + // .tmp_1, .tmp_2 := x() + // u1, n2 = .tmp_1, .tmp_2 + + println(u1) + println(n2) + +} + +// Output: +// slice[(1 int)] +// (slice[(2 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType0_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType0_filetest.gno new file mode 100644 index 00000000000..4c6ff69524d --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType0_filetest.gno @@ -0,0 +1,19 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +func main() { + var abs nat + abs = []word{0} + println(abs.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType0b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType0b_filetest.gno new file mode 100644 index 00000000000..65057890011 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType0b_filetest.gno @@ -0,0 +1,22 @@ +package main + +type ( + word int + nat []word +) + +func (n nat) add() bool { + return true +} + +func main() { + var abs nat + var b []word + b = []word{0} + abs = b + + println(abs) +} + +// Output: +// (slice[(0 main.word)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType1_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType1_filetest.gno new file mode 100644 index 00000000000..e56b4e08096 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType1_filetest.gno @@ -0,0 +1,27 @@ +package main + +type ( + word uint + nat []word +) + +// structLit +type Int struct { + neg bool + abs nat +} + +func (n nat) add() bool { + return true +} + +func main() { + z := &Int{ + neg: true, + abs: []word{0}, + } + println(z.abs.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType1b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType1b_filetest.gno new file mode 100644 index 00000000000..364070cdc0d --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType1b_filetest.gno @@ -0,0 +1,27 @@ +package main + +type ( + word uint + nat []word +) + +type Int struct { + neg bool + abs nat +} + +func (n nat) add() bool { + return true +} + +func main() { + z := &Int{ + neg: true, + } + + z.abs = []word{0} + println(z.abs.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType2_filetest.gno new file mode 100644 index 00000000000..71d3601ebef --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType2_filetest.gno @@ -0,0 +1,27 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +// map +func main() { + items := map[string]nat{} + + n := []word{0} + + // this is assignment + items["test"] = n + + r := items["test"] + + println(r.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType2b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType2b_filetest.gno new file mode 100644 index 00000000000..111d3311f98 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType2b_filetest.gno @@ -0,0 +1,26 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +// mapLit +func main() { + n := []word{0} + + items := map[string]nat{ + "test": n, + } + + r := items["test"] + + println(r.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType3_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType3_filetest.gno new file mode 100644 index 00000000000..d7ab3701b1a --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType3_filetest.gno @@ -0,0 +1,22 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +// sliceLit +func main() { + items := []nat{[]word{0}, []word{1}} + + r := items[0] + + println(r.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType3b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType3b_filetest.gno new file mode 100644 index 00000000000..5dedb25b796 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType3b_filetest.gno @@ -0,0 +1,22 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +// sliceLit2 +func main() { + items := []nat{1: []word{0}, 2: []word{1}} + + r := items[1] + + println(r.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType3c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType3c_filetest.gno new file mode 100644 index 00000000000..f4eae2d2107 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType3c_filetest.gno @@ -0,0 +1,26 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +// slice append +func main() { + var items []nat + + n := []word{0} + + items = append(items, n) + + r := items[0] + + println(r.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType3d_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType3d_filetest.gno new file mode 100644 index 00000000000..c77e6c99669 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType3d_filetest.gno @@ -0,0 +1,22 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add() bool { + return true +} + +// ArrayLit +func main() { + items := [3]nat{[]word{0}, []word{1}} + + r := items[0] + + println(r.add()) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType4_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType4_filetest.gno new file mode 100644 index 00000000000..2bba41fa617 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType4_filetest.gno @@ -0,0 +1,23 @@ +package main + +type ( + word uint + nat []word +) + +func (n nat) add(x, y nat) bool { + return true +} + +// parameter +func main() { + var abs nat + abs = []word{0} + x := []word{1} + y := []word{2} + + println(abs.add(x, y)) +} + +// Output: +// true diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType5_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType5_filetest.gno new file mode 100644 index 00000000000..dd76ef1b1b8 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType5_filetest.gno @@ -0,0 +1,24 @@ +package main + +type ( + BasicFunc func(int, int) int + MyFunc BasicFunc +) + +func (f MyFunc) Apply(a, b int) int { + return f(a, b) +} + +func main() { + basicAdd := func(a, b int) int { + return a + b + } + var myAdd MyFunc + myAdd = basicAdd + + result := myAdd.Apply(2, 3) + println(result) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno new file mode 100644 index 00000000000..e41e6c6f21f --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno @@ -0,0 +1,20 @@ +package main + +type ( + c uint + word c + nat []word +) + +func (n nat) add() bool { + return true +} + +func main() { + var abs nat + abs = []c{0} + println(abs.add()) +} + +// Error: +// main/files/assign_unnamed_type/method/declaredType6_filetest.gno:15:2: cannot use []main.c as []main.word diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno new file mode 100644 index 00000000000..673c48725b8 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno @@ -0,0 +1,20 @@ +package main + +type ( + c uint + word c + nat []word +) + +func (n nat) add() bool { + return true +} + +func main() { + var abs nat + abs = []uint{0} + println(abs.add()) +} + +// Error: +// main/files/assign_unnamed_type/method/declaredType6b_filetest.gno:15:2: cannot use []uint as []main.word 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 new file mode 100644 index 00000000000..129eb8d749a --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface2_filetest.gno @@ -0,0 +1,14 @@ +package main + +type nat []int + +func main() { + var a nat + b := interface{}(nat{1}) + a = b.(nat) + + println(b) +} + +// Output: +// (slice[(1 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno new file mode 100644 index 00000000000..92e3a1e3075 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno @@ -0,0 +1,39 @@ +package main + +type Foo interface { + Len() int +} + +type myIntSlice []int + +func (s myIntSlice) Len() int { + return len(s) +} + +func main() { + var a Foo + var b interface{ Len() int } + + var i myIntSlice + + println(a) + + i = myIntSlice{1} + a = i + + println(a) + + b = interface{ Len() int }(a) + + a = b + + println(a) + + println(b) +} + +// Output: +// (undefined) +// (slice[(1 int)] main.myIntSlice) +// (slice[(1 int)] main.myIntSlice) +// (slice[(1 int)] main.myIntSlice) diff --git a/gnovm/tests/files/assign_unnamed_type/more/assgin_two_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/assgin_two_filetest.gno new file mode 100644 index 00000000000..b2cdc14ff77 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/assgin_two_filetest.gno @@ -0,0 +1,18 @@ +package main + +type ( + nat []int + nat2 []int +) + +func main() { + a := nat{0} + b := nat2{1} + a = b + + println(a) + println(b) +} + +// Error: +// main/files/assign_unnamed_type/more/assgin_two_filetest.gno:11:2: cannot use main.nat2 as main.nat without explicit conversion diff --git a/gnovm/tests/files/assign_unnamed_type/more/assignment_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/assignment_filetest.gno new file mode 100644 index 00000000000..2972c1895fd --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/assignment_filetest.gno @@ -0,0 +1,26 @@ +package main + +type nat []int + +func main() { + var a nat + var b []int + + a = []int{0} + b = nat{1} + + println(a) + println(b) + + a = b + b = a + + println(a) + println(b) +} + +// Output: +// (slice[(0 int)] main.nat) +// slice[(1 int)] +// (slice[(1 int)] main.nat) +// slice[(1 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/more/convert_types1b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/convert_types1b_filetest.gno new file mode 100644 index 00000000000..aeaabfedd1d --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/convert_types1b_filetest.gno @@ -0,0 +1,24 @@ +package main + +type nat []int + +func main() { + var a nat + var b []int + + a = (nat)([]int{0}) + b = ([]int)(nat{1}) + println(a) + println(b) + + a = (nat)(b) + b = ([]int)(a) + println(a) + println(b) +} + +// Output: +// (slice[(0 int)] main.nat) +// slice[(1 int)] +// (slice[(1 int)] main.nat) +// slice[(1 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/more/convert_types_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/convert_types_filetest.gno new file mode 100644 index 00000000000..0ff0a61c295 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/convert_types_filetest.gno @@ -0,0 +1,21 @@ +package main + +type nat []int + +func main() { + var b []int + b = nat{1} + println(b) + + var a nat + a = []int{0} + println(a) + + b = a + println(b) +} + +// Output: +// slice[(1 int)] +// (slice[(0 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest_stdlibs.gno b/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest_stdlibs.gno new file mode 100644 index 00000000000..1bc5add0440 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest_stdlibs.gno @@ -0,0 +1,13 @@ +// PKGPATH: gno.land/r/declaredtype_test +package declaredtype_test + +import ( + "gno.land/r/demo/tests" +) + +func main() { + println(tests.GetZeroType()) +} + +// Output: +// (slice[(0 gno.land/r/demo/tests.Word)] gno.land/r/demo/tests.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/more/declaredtype2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/declaredtype2_filetest.gno new file mode 100644 index 00000000000..c697abc0584 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/declaredtype2_filetest.gno @@ -0,0 +1,33 @@ +package main + +type ( + nat []Word + Word uint +) + +func main() { + var abs []Word + abs = nat{0} // abs is a not named array + println(abs) + println() + a := []Word{0} + b := nat{0} + c := a + d := b + e := abs + + println(a) + println(b) + println(c) + println(d) + println(e) +} + +// Output: +// slice[(0 main.Word)] +// +// slice[(0 main.Word)] +// (slice[(0 main.Word)] main.nat) +// slice[(0 main.Word)] +// (slice[(0 main.Word)] main.nat) +// slice[(0 main.Word)] 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 new file mode 100644 index 00000000000..bb590f60dae --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/default_value_filetest.gno @@ -0,0 +1,31 @@ +package main + +type ( + nat []int + nmap map[int]int + nfunc func() +) + +func main() { + var u1 []int + var n2 nat + var m map[int]int + var m2 nmap + var f func() + var f2 nfunc + + println(u1) + println(n2) + println(m) + println(m2) + println(f) + println(f2) +} + +// Output: +// (nil []int) +// (nil main.nat) +// (nil map[int]int) +// (nil main.nmap) +// nil func()() +// (nil main.nfunc) diff --git a/gnovm/tests/files/assign_unnamed_type/more/errors2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/errors2_filetest.gno new file mode 100644 index 00000000000..2588b904a77 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/errors2_filetest.gno @@ -0,0 +1,17 @@ +package main + +import "errors" + +func makeError() error { + return errors.New("some error") +} + +func main() { + var a error + a = makeError() + b := a.Error() + println(a) +} + +// Output: +// some error diff --git a/gnovm/tests/files/assign_unnamed_type/more/method38d_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/method38d_filetest.gno new file mode 100644 index 00000000000..09588bd5c10 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/method38d_filetest.gno @@ -0,0 +1,21 @@ +package main + +type ( + nat []Word + Word uint +) + +func (n nat) add() nat { + return []Word{0} +} + +func main() { + var abs nat + abs = []Word{0} + println(abs) + println(abs.add().add()) +} + +// Output: +// (slice[(0 main.Word)] main.nat) +// (slice[(0 main.Word)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/more/method38e_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/method38e_filetest.gno new file mode 100644 index 00000000000..f140d4b7291 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/method38e_filetest.gno @@ -0,0 +1,22 @@ +package main + +type ( + nat []Word + Word uint +) + +func (n nat) add() []Word { + return nat{0} +} + +func main() { + var abs nat + abs = []Word{0} + println(abs) + println(abs.add()) + // println(abs.add().add()) // it should build failed. +} + +// Output: +// (slice[(0 main.Word)] main.nat) +// slice[(0 main.Word)] diff --git a/gnovm/tests/files/assign_unnamed_type/more/method38e_filetest0.gno b/gnovm/tests/files/assign_unnamed_type/more/method38e_filetest0.gno new file mode 100644 index 00000000000..f140d4b7291 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/method38e_filetest0.gno @@ -0,0 +1,22 @@ +package main + +type ( + nat []Word + Word uint +) + +func (n nat) add() []Word { + return nat{0} +} + +func main() { + var abs nat + abs = []Word{0} + println(abs) + println(abs.add()) + // println(abs.add().add()) // it should build failed. +} + +// Output: +// (slice[(0 main.Word)] main.nat) +// slice[(0 main.Word)] diff --git a/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno new file mode 100644 index 00000000000..2c41bbb0a97 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno @@ -0,0 +1,34 @@ +package main + +type ( + nat []Word + Word uint +) + +func (n nat) add(ws []Word) interface{} { + println(ws) + + return ws +} + +func (n nat) add2(ws nat) interface{} { + println(ws) + + return ws +} + +func main() { + var abs nat + abs = []Word{0} + + println(abs.add(abs)) + println() + println(abs.add2(abs)) +} + +// Output: +// slice[(0 main.Word)] +// slice[(0 main.Word)] +// +// (slice[(0 main.Word)] main.nat) +// (slice[(0 main.Word)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno new file mode 100644 index 00000000000..5bdd878c146 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno @@ -0,0 +1,241 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "fmt" +) + +type ( + word uint + nat []word +) + +var zero *Int + +// structLit +type Int struct { + abs nat +} + +func main() { + zero = &Int{ + abs: []word{0}, + } + a := zero.abs + println(a) +} + +// Output: +// (slice[(0 gno.land/r/test.word)] gno.land/r/test.nat) + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.word" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.nat" +// }, +// "V": { +// "@type": "/gno.SliceValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "e256933ba4dfda233a7edb0902880d554118ba6e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, +// "Length": "1", +// "Maxcap": "1", +// "Offset": "0" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.Int" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2b8a024c53e94431e6203115feaf86b36413d7b2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "3", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "0", +// "File": "", +// "Line": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.PrimitiveType", +// "value": "2048" +// }, +// "Methods": [], +// "Name": "word", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.SliceType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.word" +// }, +// "Vrd": false +// }, +// "Methods": [], +// "Name": "nat", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.Int" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "3c89d875f7d6daa94113aa4c7e03432ba56202c2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "abs", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.nat" +// } +// } +// ], +// "PkgPath": "gno.land/r/test" +// }, +// "Methods": [], +// "Name": "Int", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "main.gno", +// "Line": "20", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// } +// ] +// } diff --git a/gnovm/tests/files/assign_unnamed_type/more/recover6_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/recover6_filetest.gno new file mode 100644 index 00000000000..0b304369764 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/recover6_filetest.gno @@ -0,0 +1,30 @@ +package main + +import ( + "errors" +) + +func main() { + println(f(false)) + println(f(true)) +} + +func f(dopanic bool) (err error) { + defer func() { + if x := recover(); x != nil { + err = x.(error) + } + }() + q(dopanic) + return +} + +func q(dopanic bool) { + if dopanic { + panic(errors.New("wtf")) + } +} + +// Output: +// undefined +// wtf diff --git a/gnovm/tests/files/assign_unnamed_type/more/return2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/return2_filetest.gno new file mode 100644 index 00000000000..2bdfdb474fe --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/return2_filetest.gno @@ -0,0 +1,16 @@ +package main + +func x() (int, int) { + return 1, 2 +} + +func main() { + var a, b int + a, b = x() + println(a) + println(b) +} + +// Output: +// 1 +// 2 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 new file mode 100644 index 00000000000..61ae8208a4a --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/return_interface1_filetest.gno @@ -0,0 +1,15 @@ +package main + +func x1() interface{} { + a := "1" + return a +} + +func main() { + var a uint + a = x1() // should fail + println("1") +} + +// Error: +// 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 new file mode 100644 index 00000000000..11ac22370b5 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/return_interface_filetest.gno @@ -0,0 +1,19 @@ +package main + +type nat []int + +func x() interface{} { + a := nat{0} + return a +} + +func main() { + var a nat + + a = x() + + println(a) +} + +// Error: +// 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 new file mode 100644 index 00000000000..cf5be970423 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/more/return_select_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat []int + +func x() interface{} { + a := nat{0} + return a +} + +func (n nat) double() nat { + m := append(n, n...) + return m +} + +func main() { + a := x() + + b := a.(nat).double().double() + println(a) + println(b) +} + +// Output: +// (slice[(0 int)] main.nat) +// (slice[(0 int),(0 int),(0 int),(0 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/type40.gno b/gnovm/tests/files/assign_unnamed_type/type40.gno new file mode 100644 index 00000000000..324b33290f5 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/type40.gno @@ -0,0 +1,20 @@ +package main + +type Set map[string]struct{} + +func NewSet(items ...string) Set { + return map[string]struct{}{} +} + +func (s Set) Has(key string) bool { + _, ok := s[key] + return ok +} + +func main() { + s := NewSet() + println(s.Has("a")) +} + +// Output: +// false diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype0_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype0_filetest.gno new file mode 100644 index 00000000000..5876111b324 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype0_filetest.gno @@ -0,0 +1,17 @@ +package main + +type ( + nat []word + word int +) + +func main() { + var a nat + b := []word{0} + a = b + + println(a) +} + +// Output: +// (slice[(0 main.word)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno new file mode 100644 index 00000000000..0c7b03df575 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno @@ -0,0 +1,17 @@ +package main + +type ( + nat []int + word int +) + +func main() { + var a nat + b := []word{0} + a = b + + println(a) +} + +// Error: +// main/files/assign_unnamed_type/unnamedtype0b_filetest.gno:11:2: cannot use []main.word as []int diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype1_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype1_filetest.gno new file mode 100644 index 00000000000..d8cb730f90f --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype1_filetest.gno @@ -0,0 +1,24 @@ +package main + +type nat []int + +func main() { + var a nat + a = []int{0} + b := []int{1} + + println(a) + println(b) + + a = nat{0} + b = a + + println(a) + println(b) +} + +// Output: +// (slice[(0 int)] main.nat) +// slice[(1 int)] +// (slice[(0 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype1a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype1a_filetest.gno new file mode 100644 index 00000000000..63cc382039e --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype1a_filetest.gno @@ -0,0 +1,23 @@ +package main + +type nat []int + +func (n nat) zero(num []int) { + println(num) +} + +func (n nat) one(num nat) { + println(num) +} + +func main() { + var a nat + a = []int{} + + a.zero(nat{0}) + a.one([]int{1}) +} + +// Output: +// slice[(0 int)] +// (slice[(1 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype1b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype1b_filetest.gno new file mode 100644 index 00000000000..c440cf974cd --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype1b_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat []int + +func (n nat) zero() nat { + return []int{0} +} + +func (n nat) one() []int { + return nat{1} +} + +func main() { + var a nat + a = []int{} + + println(a.zero()) + println(a.zero().zero()) + println(a.zero().one()) + println(a.one()) +} + +// Output: +// (slice[(0 int)] main.nat) +// (slice[(0 int)] main.nat) +// slice[(1 int)] +// slice[(1 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno new file mode 100644 index 00000000000..aa5533bd0e8 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat []int + +func (n nat) zero() interface{} { + return []int{0} +} + +func (n nat) one() interface{} { + return nat{1} +} + +func main() { + var a nat + a = []int{} + + println(a.zero()) + println(a.one()) + println(a.one().(nat).zero()) +} + +// Output: +// slice[(0 int)] +// (slice[(1 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype2_filetest.gno new file mode 100644 index 00000000000..132242f83ea --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype2_filetest.gno @@ -0,0 +1,23 @@ +package main + +type nat [1]int + +func main() { + var a nat + a = [1]int{0} + b := [1]int{1} + + println(a) + println(b) + a = nat{0} + b = a + + println(a) + println(b) +} + +// Output: +// (array[(0 int)] main.nat) +// array[(1 int)] +// (array[(0 int)] main.nat) +// array[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype2a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype2a_filetest.gno new file mode 100644 index 00000000000..5f25b9333ea --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype2a_filetest.gno @@ -0,0 +1,23 @@ +package main + +type nat [1]int + +func (n nat) zero(num [1]int) { + println(num) +} + +func (n nat) one(num nat) { + println(num) +} + +func main() { + var a nat + a = [1]int{} + + a.zero(nat{0}) + a.one([1]int{1}) +} + +// Output: +// array[(0 int)] +// (array[(1 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype2b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype2b_filetest.gno new file mode 100644 index 00000000000..0e7c4992fa5 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype2b_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat [1]int + +func (n nat) zero() nat { + return [1]int{0} +} + +func (n nat) one() [1]int { + return nat{1} +} + +func main() { + var a nat + a = [1]int{} + + println(a.zero()) + println(a.zero().zero()) + println(a.zero().one()) + println(a.one()) +} + +// Output: +// (array[(0 int)] main.nat) +// (array[(0 int)] main.nat) +// array[(1 int)] +// array[(1 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno new file mode 100644 index 00000000000..9fc0e0f06a4 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat [1]int + +func (n nat) zero() interface{} { + return [1]int{0} +} + +func (n nat) one() interface{} { + return nat{1} +} + +func main() { + var a nat + a = [1]int{} + + println(a.zero()) + println(a.one()) + println(a.one().(nat).zero()) +} + +// Output: +// array[(0 int)] +// (array[(1 int)] main.nat) +// array[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype3_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype3_filetest.gno new file mode 100644 index 00000000000..ccc5cc2bb75 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype3_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat struct { + num int +} + +func main() { + var a nat + a = struct{ num int }{0} + b := struct{ num int }{1} + + println(a) + println(b) + a = nat{0} + b = a + + println(a) + println(b) +} + +// Output: +// (struct{(0 int)} main.nat) +// struct{(1 int)} +// (struct{(0 int)} main.nat) +// struct{(0 int)} diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype3a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype3a_filetest.gno new file mode 100644 index 00000000000..06c2e3f5a4d --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype3a_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat struct { + num int +} + +func (n nat) zero(num struct{ num int }) { + println(num) +} + +func (n nat) one(num nat) { + println(num) +} + +func main() { + var a nat + a = struct{ num int }{0} + + a.zero(nat{0}) + a.one(struct{ num int }{1}) +} + +// Output: +// struct{(0 int)} +// (struct{(1 int)} main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype3b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype3b_filetest.gno new file mode 100644 index 00000000000..e9c9b80418e --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype3b_filetest.gno @@ -0,0 +1,29 @@ +package main + +type nat struct { + num int +} + +func (n nat) zero() nat { + return struct{ num int }{0} +} + +func (n nat) one() struct{ num int } { + return nat{1} +} + +func main() { + var a nat + a = struct{ num int }{} + + println(a.zero()) + println(a.zero().zero()) + println(a.zero().one()) + println(a.one()) +} + +// Output: +// (struct{(0 int)} main.nat) +// (struct{(0 int)} main.nat) +// struct{(1 int)} +// struct{(1 int)} diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno new file mode 100644 index 00000000000..5d506ab9566 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat struct { + num int +} + +func (n nat) zero() interface{} { + return struct{ num int }{0} +} + +func (n nat) one() interface{} { + return nat{1} +} + +func main() { + var a nat + a = struct{ num int }{} + + println(a.zero()) + println(a.one()) + println(a.one().(nat).zero()) +} + +// Output: +// struct{(0 int)} +// (struct{(1 int)} main.nat) +// struct{(0 int)} diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype4_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype4_filetest.gno new file mode 100644 index 00000000000..f384f71975f --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype4_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat map[string]int + +func main() { + var a nat + + a = map[string]int{ + "zero": 0, + } + b := map[string]int{ + "one": 1, + } + println(a) + println(b) + + a = nat{"zeor": 0} + b = a + println(a) + println(b) +} + +// Output: +// (map{("zero" string):(0 int)} main.nat) +// map{("one" string):(1 int)} +// (map{("zeor" string):(0 int)} main.nat) +// map{("zeor" string):(0 int)} diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype4a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype4a_filetest.gno new file mode 100644 index 00000000000..6c3b0856df9 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype4a_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat map[string]int + +func (n nat) zero(num map[string]int) { + println(num) +} + +func (n nat) one(num nat) { + println(num) +} + +func main() { + var a nat + a = map[string]int{ + "zero": 0, + } + + a.zero(nat{"zero": 0}) + a.one(map[string]int{ + "zero": 1, + }) +} + +// Output: +// map{("zero" string):(0 int)} +// (map{("zero" string):(1 int)} main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype4b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype4b_filetest.gno new file mode 100644 index 00000000000..15975fc588e --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype4b_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat map[string]int + +func (n nat) zero() nat { + return map[string]int{"zero": 0} +} + +func (n nat) one() map[string]int { + return nat{"one": 1} +} + +func main() { + var a nat + a = map[string]int{"zero": 0} + + println(a.zero()) + println(a.zero().zero()) + println(a.zero().one()) + println(a.one()) +} + +// Output: +// (map{("zero" string):(0 int)} main.nat) +// (map{("zero" string):(0 int)} main.nat) +// map{("one" string):(1 int)} +// map{("one" string):(1 int)} diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno new file mode 100644 index 00000000000..d6efcaded75 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat map[string]int + +func (n nat) zero() interface{} { + return map[string]int{"zero": 0} +} + +func (n nat) one() interface{} { + return nat{"one": 1} +} + +func main() { + var a nat + a = map[string]int{"zero": 0} + + println(a.zero()) + println(a.one()) + println(a.one().(nat).zero()) +} + +// Output: +// map{("zero" string):(0 int)} +// (map{("one" string):(1 int)} main.nat) +// map{("zero" string):(0 int)} diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno new file mode 100644 index 00000000000..583e2f12bd8 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno @@ -0,0 +1,41 @@ +package main + +type op func(int) int + +func inc(n int) int { + n = n + 1 + return n +} + +func dec(n int) int { + n = n - 1 + return n +} + +func main() { + var a op + a = inc + + b := func(n int) int { + return n + } + c := dec + + println(a(0), a) + println(b(0), b) + println(c(0), c) + + a, b, c = b, c, a + + println(a(0), a) + println(b(0), b) + println(c(0), c) +} + +// Output: +// 1 (inc main.op) +// 0 +// -1 dec +// 0 ( 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 new file mode 100644 index 00000000000..e14e64e4dfd --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno @@ -0,0 +1,32 @@ +package main + +type op func(int) int + +func exec1(opFn func(int) int) { + println(opFn) + println(opFn(0)) +} + +func exec2(opFn op) { + println(opFn) + println(opFn(0)) +} + +func main() { + var inc op + inc = func(n int) int { + n = n + 1 + return n + } + dec := func(n int) int { + n = n - 1 + return n + } + exec1(inc) + exec2(dec) +} + +// Output: +// 1 +// ( main.op) +// -1 diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5b_filetest.gno new file mode 100644 index 00000000000..80f8c224e6c --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5b_filetest.gno @@ -0,0 +1,30 @@ +package main + +type op func(int) int + +func popFn1() op { + return func(n int) int { + n = n + 1 + return n + } +} + +func popFn2() func(int) int { + var dec op + + dec = func(n int) int { + n = n - 1 + return n + } + + return dec +} + +func main() { + println(popFn1()(0)) + println(popFn2()(0)) +} + +// Output: +// 1 +// -1 diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno new file mode 100644 index 00000000000..aa5533bd0e8 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat []int + +func (n nat) zero() interface{} { + return []int{0} +} + +func (n nat) one() interface{} { + return nat{1} +} + +func main() { + var a nat + a = []int{} + + println(a.zero()) + println(a.one()) + println(a.one().(nat).zero()) +} + +// Output: +// slice[(0 int)] +// (slice[(1 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno new file mode 100644 index 00000000000..41c72c390a1 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno @@ -0,0 +1,24 @@ +package main + +type nat []interface{} + +func main() { + var a nat + a = []interface{}{0} + b := []interface{}{1} + + println(a) + println(b) + + a = nat{0} + b = a + + println(a) + println(b) +} + +// Output: +// (slice[(0 int)] main.nat) +// slice[(1 int)] +// (slice[(0 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno new file mode 100644 index 00000000000..37e3951df55 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno @@ -0,0 +1,24 @@ +package main + +type nat []interface{} + +func (n nat) zero(num []interface{}) { + println(num) +} + +func (n nat) one(num nat) { + println(num) +} + +func main() { + var a nat + a = []interface{}{} + println(a) + a.zero(nat{0}) + a.one([]interface{}{1}) +} + +// Output: +// (slice[] main.nat) +// slice[(0 int)] +// (slice[(1 int)] main.nat) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno new file mode 100644 index 00000000000..e57821f8b7d --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno @@ -0,0 +1,27 @@ +package main + +type nat []interface{} + +func (n nat) zero() nat { + return []interface{}{0} +} + +func (n nat) one() []interface{} { + return nat{1} +} + +func main() { + var a nat + a = []interface{}{} + + println(a.zero()) + println(a.zero().zero()) + println(a.zero().one()) + println(a.one()) +} + +// Output: +// (slice[(0 int)] main.nat) +// (slice[(0 int)] main.nat) +// slice[(1 int)] +// slice[(1 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno new file mode 100644 index 00000000000..1c17c204fe8 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno @@ -0,0 +1,25 @@ +package main + +type nat []interface{} + +func (n nat) zero() interface{} { + return []interface{}{0} +} + +func (n nat) one() interface{} { + return nat{1} +} + +func main() { + var a nat + a = []interface{}{} + + println(a.zero()) + println(a.one()) + println(a.one().(nat).zero()) +} + +// Output: +// slice[(0 int)] +// (slice[(1 int)] main.nat) +// slice[(0 int)] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype7_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype7_filetest.gno new file mode 100644 index 00000000000..939428bd192 --- /dev/null +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype7_filetest.gno @@ -0,0 +1,15 @@ +package main + +type mychan chan int + +// chan int is unmamed +func main() { + var n mychan = nil + var u chan int = nil + n = u + + println(n) +} + +// Output: +// (nil main.mychan) diff --git a/gnovm/tests/files/bltn0.gno b/gnovm/tests/files/bltn0.gno index 2a047303a31..7b65e71f50e 100644 --- a/gnovm/tests/files/bltn0.gno +++ b/gnovm/tests/files/bltn0.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// main/files/bltn0.gno:4: use of builtin println not in function call +// main/files/bltn0.gno:4:7: use of builtin println not in function call diff --git a/gnovm/tests/files/bool6.gno b/gnovm/tests/files/bool6.gno index ad4d832036c..94b6a1d76de 100644 --- a/gnovm/tests/files/bool6.gno +++ b/gnovm/tests/files/bool6.gno @@ -9,4 +9,4 @@ func X() string { } // Error: -// main/files/bool6.gno:8: operands of boolean operators must evaluate to boolean typed values +// main/files/bool6.gno:8:9: operator || not defined on: StringKind diff --git a/gnovm/tests/files/comp4.gno b/gnovm/tests/files/comp4.gno new file mode 100644 index 00000000000..501cffeb871 --- /dev/null +++ b/gnovm/tests/files/comp4.gno @@ -0,0 +1,32 @@ +package main + +type S struct { +} + +func main() { + x := (*S)(nil) + println("x == x", x == x) + println("x == nil", x == nil) + println("nil == x", nil == x) + + var y interface{} = (*S)(nil) + println("y == y", y == y) + println("y == nil", y == nil) + println("nil == y", nil == y) + + y = nil + println("y == y", y == y) + println("y == nil", y == nil) + println("nil == y", nil == y) +} + +// Output: +// x == x true +// x == nil true +// nil == x true +// y == y true +// y == nil false +// nil == y false +// y == y true +// y == nil true +// nil == y true diff --git a/gnovm/tests/files/comp5.gno b/gnovm/tests/files/comp5.gno new file mode 100644 index 00000000000..827800f2a67 --- /dev/null +++ b/gnovm/tests/files/comp5.gno @@ -0,0 +1,23 @@ +package main + +type S struct { +} + +func main() { + x := (*S)(nil) + var y interface{} = (*S)(nil) + var znil interface{} = nil + + println("y == znil", y == znil) + println("znil == y", znil == y) + + y = nil + println("y == znil", y == znil) + println("znil == y", znil == y) +} + +// Output: +// y == znil false +// znil == y false +// y == znil true +// znil == y true diff --git a/gnovm/tests/files/const9.gno b/gnovm/tests/files/const9.gno index 21bb7dbc753..d67aaf63137 100644 --- a/gnovm/tests/files/const9.gno +++ b/gnovm/tests/files/const9.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/const9.gno:1: constant definition loop with b +// main/files/const9.gno:5:2: constant definition loop with b diff --git a/gnovm/tests/files/convert4.gno b/gnovm/tests/files/convert4.gno index 6ad748499ed..d7ab4905f62 100644 --- a/gnovm/tests/files/convert4.gno +++ b/gnovm/tests/files/convert4.gno @@ -4,4 +4,4 @@ func main() { println(int(nil)) } -// error: cannot convert (undefined) to int +// Error: cannot convert (undefined) to int diff --git a/gnovm/tests/files/convert5.gno b/gnovm/tests/files/convert5.gno index acd33889ff0..74063709110 100644 --- a/gnovm/tests/files/convert5.gno +++ b/gnovm/tests/files/convert5.gno @@ -6,4 +6,4 @@ func main() { println(ints) } -// error: cannot convert (undefined) to int +// Error: cannot convert (undefined) to int diff --git a/gnovm/tests/files/extern/p1/s2.gno b/gnovm/tests/files/extern/p1/s2.gno index cfed6498051..01aa3f47da1 100644 --- a/gnovm/tests/files/extern/p1/s2.gno +++ b/gnovm/tests/files/extern/p1/s2.gno @@ -3,5 +3,3 @@ package p1 import "math/rand" var Uint32 = rand.Uint32 - -func init() { rand.Seed(1) } diff --git a/gnovm/tests/files/float0.gno b/gnovm/tests/files/float0.gno index 07a6db74550..7353235587a 100644 --- a/gnovm/tests/files/float0.gno +++ b/gnovm/tests/files/float0.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// main/files/float0.gno:4: cannot convert (const (1.2 bigdec)) to integer type +// main/files/float0.gno:4:7: cannot convert (const (1.2 bigdec)) to integer type diff --git a/gnovm/tests/files/float5_stdlibs.gno b/gnovm/tests/files/float5_stdlibs.gno index b3d8cd84713..564cddb0309 100644 --- a/gnovm/tests/files/float5_stdlibs.gno +++ b/gnovm/tests/files/float5_stdlibs.gno @@ -9,9 +9,9 @@ func main() { println(math.MaxFloat64, math.Float64bits(math.MaxFloat64)) } +// NOTE: 0x7f7fffff is 2139095039 +// NOTE: 0x7fefffffffffffff is 9218868437227405311 + // Output: // 3.4028234663852886e+38 2139095039 // 1.7976931348623157e+308 9218868437227405311 - -// NOTE: 0x7f7fffff is 2139095039 -// NOTE: 0x7fefffffffffffff is 9218868437227405311 diff --git a/gnovm/tests/files/for19.gno b/gnovm/tests/files/for19.gno new file mode 100644 index 00000000000..ce02c5fb655 --- /dev/null +++ b/gnovm/tests/files/for19.gno @@ -0,0 +1,25 @@ +package main + +func main() { + v := T + i := 0 + for v { + if i > 2 { + break + } + println(i) + i++ + } +} + +type C bool + +const ( + F C = false + T C = true +) + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/for7.gno b/gnovm/tests/files/for7.gno index 92ad5f3ce34..ed3d2f729c8 100644 --- a/gnovm/tests/files/for7.gno +++ b/gnovm/tests/files/for7.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// main/files/for7.gno:4: cannot use int as bool +// main/files/for7.gno:4:2: expected typed bool kind, but got IntKind diff --git a/gnovm/tests/files/fun21.gno b/gnovm/tests/files/fun21.gno index 630e4856616..25725ccd8c4 100644 --- a/gnovm/tests/files/fun21.gno +++ b/gnovm/tests/files/fun21.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/fun21.gno:4: expected 1 return values; got 0 +// main/files/fun21.gno:4:2: expected 1 return values; got 0 diff --git a/gnovm/tests/files/fun22.gno b/gnovm/tests/files/fun22.gno index 36c131a1cc7..671f2d1a1ed 100644 --- a/gnovm/tests/files/fun22.gno +++ b/gnovm/tests/files/fun22.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun22.gno:6: wrong argument count in call to time.Date; want 8 got 0 +// main/files/fun22.gno:6:2: wrong argument count in call to time.Date; want 8 got 0 diff --git a/gnovm/tests/files/fun23.gno b/gnovm/tests/files/fun23.gno index 659811ba1be..17bd5b3e0cc 100644 --- a/gnovm/tests/files/fun23.gno +++ b/gnovm/tests/files/fun23.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun23.gno:3: expected 0 return values; got 1 +// main/files/fun23.gno:3:17: expected 0 return values; got 1 diff --git a/gnovm/tests/files/fun24.gno b/gnovm/tests/files/fun24.gno index 42468ff087e..a753e94c69f 100644 --- a/gnovm/tests/files/fun24.gno +++ b/gnovm/tests/files/fun24.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun24.gno:3: cannot convert StringKind to IntKind +// main/files/fun24.gno:3:28: cannot use untyped string as IntKind diff --git a/gnovm/tests/files/fun25.gno b/gnovm/tests/files/fun25.gno index 25b31eedb1f..f5ee03fbe58 100644 --- a/gnovm/tests/files/fun25.gno +++ b/gnovm/tests/files/fun25.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun25.gno:3: cannot use string as int +// main/files/fun25.gno:3:35: cannot use string as int diff --git a/gnovm/tests/files/fun27.gno b/gnovm/tests/files/fun27.gno index 4e6e25e1065..87a878fd24a 100644 --- a/gnovm/tests/files/fun27.gno +++ b/gnovm/tests/files/fun27.gno @@ -13,4 +13,4 @@ func main() { } // Error: -// main/files/fun27.gno:8: bigint does not implement main.Foo +// main/files/fun27.gno:8:2: bigint does not implement main.Foo (missing method foo) diff --git a/gnovm/tests/files/heap_item_value.gno b/gnovm/tests/files/heap_item_value.gno new file mode 100644 index 00000000000..40ec05d3ba1 --- /dev/null +++ b/gnovm/tests/files/heap_item_value.gno @@ -0,0 +1,178 @@ +// PKGPATH: gno.land/r/test +package test + +type S struct { + A int +} + +var a, b *S + +func main() { + a = new(S) + a.A = 4 + b = a +} + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Fields": [ +// { +// "N": "BAAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "IsEscaped": true, +// "ModTime": "0", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8c001dde13b1f4dc01fc6d3a5bb4bc0cdfe2a50b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "3", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "0", +// "File": "", +// "Line": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "A", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// } +// ], +// "PkgPath": "gno.land/r/test" +// }, +// "Methods": [], +// "Name": "S", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "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:4" +// }, +// "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:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "main.gno", +// "Line": "10", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// } +// ] +// } diff --git a/gnovm/tests/files/heap_item_value_init.gno b/gnovm/tests/files/heap_item_value_init.gno new file mode 100644 index 00000000000..72f065326f1 --- /dev/null +++ b/gnovm/tests/files/heap_item_value_init.gno @@ -0,0 +1,185 @@ +// PKGPATH: gno.land/r/test +package test + +type S struct { + A *int +} + +var a, b *S + +func init() { + a = new(S) + a.A = new(int) + *a.A = 4 +} + +func main() { + b = a +} + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "6", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "0", +// "File": "", +// "Line": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "A", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// } +// } +// ], +// "PkgPath": "gno.land/r/test" +// }, +// "Methods": [], +// "Name": "S", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "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:4" +// }, +// "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:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.3", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "main.gno", +// "Line": "10", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "main.gno", +// "Line": "16", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// } +// ] +// } diff --git a/gnovm/tests/files/if2.gno b/gnovm/tests/files/if2.gno index 88261c520c9..128ab308713 100644 --- a/gnovm/tests/files/if2.gno +++ b/gnovm/tests/files/if2.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/if2.gno:7: cannot use int as bool +// main/files/if2.gno:7:2: expected typed bool kind, but got IntKind diff --git a/gnovm/tests/files/if8.gno b/gnovm/tests/files/if8.gno new file mode 100644 index 00000000000..c013896d9d3 --- /dev/null +++ b/gnovm/tests/files/if8.gno @@ -0,0 +1,18 @@ +package main + +func main() { + v := T + if v { + println("true") + } +} + +type C bool + +const ( + F C = false + T C = true +) + +// Output: +// true diff --git a/gnovm/tests/files/import10.gno b/gnovm/tests/files/import10.gno index 14df8bcbc43..9ee8e78d457 100644 --- a/gnovm/tests/files/import10.gno +++ b/gnovm/tests/files/import10.gno @@ -8,4 +8,4 @@ func main() { } // Error: -// main/files/import10.gno:7: package fmt cannot only be referred to in a selector expression +// main/files/import10.gno:7:10: package fmt cannot only be referred to in a selector expression diff --git a/gnovm/tests/files/import4.gno b/gnovm/tests/files/import4.gno index dc52adafc31..91364f45389 100644 --- a/gnovm/tests/files/import4.gno +++ b/gnovm/tests/files/import4.gno @@ -5,4 +5,4 @@ import "github.com/gnolang/gno/_test/p1" func main() { println("num:", p1.Uint32()) } // Output: -// num: 2596996162 +// num: 956301160 diff --git a/gnovm/tests/files/import6.gno b/gnovm/tests/files/import6.gno index da5dbfbd3b2..6909e1ad923 100644 --- a/gnovm/tests/files/import6.gno +++ b/gnovm/tests/files/import6.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" (through [github.com/gnolang/gno/_test/c1 github.com/gnolang/gno/_test/c2]) +// github.com/gnolang/gno/_test/c2/c2.gno:3:1: import cycle detected: "github.com/gnolang/gno/_test/c1" (through [github.com/gnolang/gno/_test/c1 github.com/gnolang/gno/_test/c2]) diff --git a/gnovm/tests/files/init1.gno b/gnovm/tests/files/init1.gno index fa1c74d24f2..0fc0df92d45 100644 --- a/gnovm/tests/files/init1.gno +++ b/gnovm/tests/files/init1.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/init1.gno:8: name init not declared +// main/files/init1.gno:8:2: name init not declared diff --git a/gnovm/tests/files/invalid_labels0.gno b/gnovm/tests/files/invalid_labels0.gno new file mode 100644 index 00000000000..65e072c9871 --- /dev/null +++ b/gnovm/tests/files/invalid_labels0.gno @@ -0,0 +1,14 @@ +package main + +func main() {} +func invalidLabel() { +FirstLoop: + for i := 0; i < 10; i++ { + } + for i := 0; i < 10; i++ { + break FirstLoop + } +} + +// Error: +// main/files/invalid_labels0.gno:9:3: cannot find branch label "FirstLoop" diff --git a/gnovm/tests/files/invalid_labels1.gno b/gnovm/tests/files/invalid_labels1.gno new file mode 100644 index 00000000000..40513c338eb --- /dev/null +++ b/gnovm/tests/files/invalid_labels1.gno @@ -0,0 +1,12 @@ +package main + +func main() {} + +func undefinedLabel() { + for i := 0; i < 10; i++ { + break UndefinedLabel + } +} + +// Error: +// files/invalid_labels1.gno:7:9: label UndefinedLabel undefined diff --git a/gnovm/tests/files/invalid_labels2.gno b/gnovm/tests/files/invalid_labels2.gno new file mode 100644 index 00000000000..4d6b09c0172 --- /dev/null +++ b/gnovm/tests/files/invalid_labels2.gno @@ -0,0 +1,15 @@ +package main + +func main() {} + +func labelOutsideScope() { + for i := 0; i < 10; i++ { + continue FirstLoop + } +FirstLoop: + for i := 0; i < 10; i++ { + } +} + +// Error: +// main/files/invalid_labels2.gno:7:3: cannot find branch label "FirstLoop" diff --git a/gnovm/tests/files/invalid_labels3.gno b/gnovm/tests/files/invalid_labels3.gno new file mode 100644 index 00000000000..3d36b010fa6 --- /dev/null +++ b/gnovm/tests/files/invalid_labels3.gno @@ -0,0 +1,12 @@ +package main + +func main() {} + +func invalidLabelStatement() { + if true { + break InvalidLabel + } +} + +// Error: +// files/invalid_labels3.gno:7:9: label InvalidLabel undefined diff --git a/gnovm/tests/files/issue-2449.gno b/gnovm/tests/files/issue-2449.gno new file mode 100644 index 00000000000..6af4dbafc6c --- /dev/null +++ b/gnovm/tests/files/issue-2449.gno @@ -0,0 +1,65 @@ +// PKGPATH: gno.land/r/evt_test +package evt_test + +type Event struct { + name string +} + +var deletionEvents = []*Event{ + {name: "event1"}, + {name: "event2"}, + {name: "event3"}, + {name: "event4"}, +} + +var insertEvents = []*Event{ + {name: "event1"}, + {name: "event2"}, +} + +var appendEvents = []*Event{ + {name: "event1"}, +} + +func DelEvent(name string) { + for i, event := range deletionEvents { + if event.name == name { + deletionEvents = append(deletionEvents[:i], deletionEvents[i+1:]...) + return + } + } +} + +func InsertEvent(name string) { + insertEvents = append(insertEvents[:1], append([]*Event{{name: name}}, insertEvents[1:]...)...) +} + +func AppendEvent(name string) { + appendEvents = append(appendEvents, &Event{name: name}) +} + +func printEvents(events []*Event) { + for _, event := range events { + println(event.name) + } +} + +func main() { + DelEvent("event2") + InsertEvent("event1.5") + AppendEvent("event2") + + printEvents(deletionEvents) + printEvents(insertEvents) + printEvents(appendEvents) +} + +// Output: +// event1 +// event3 +// event4 +// event1 +// event1.5 +// event2 +// event1 +// event2 diff --git a/gnovm/tests/files/issue_1326.gno b/gnovm/tests/files/issue_1326.gno new file mode 100644 index 00000000000..a86058a63e0 --- /dev/null +++ b/gnovm/tests/files/issue_1326.gno @@ -0,0 +1,64 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "strconv" +) + +func init() { + New() + println(Delta()) +} + +func main() { + println(Delta()) +} + +type Move struct { + N1, N2, N3 byte +} + +type S struct { + Moves []Move +} + +func (s S) clone() S { + mv := s.Moves + return S{Moves: mv} +} + +func (olds S) change() S { + s := olds.clone() + + counter++ + s.Moves = append([]Move{}, s.Moves...) + s.Moves = append(s.Moves, Move{counter, counter, counter}) + return s +} + +var ( + el *S + counter byte +) + +func New() { + el = new(S) +} + +func Delta() string { + n := el.change() + *el = n + return Values() +} + +func Values() string { + s := "" + for _, val := range el.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} + +// Output: +// 1,1,1; +// 1,1,1;2,2,2; diff --git a/gnovm/tests/files/issue_558b.gno b/gnovm/tests/files/issue_558b_stdlibs.gno similarity index 100% rename from gnovm/tests/files/issue_558b.gno rename to gnovm/tests/files/issue_558b_stdlibs.gno diff --git a/gnovm/tests/files/math_native.gno b/gnovm/tests/files/math_native.gno new file mode 100644 index 00000000000..54bddcde1f3 --- /dev/null +++ b/gnovm/tests/files/math_native.gno @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "math" +) + +func main() { + var a float64 = 2.0 + fmt.Println(math.Pi * a) +} + +// Output: +// 6.283185307179586 diff --git a/gnovm/tests/files/op7.gno b/gnovm/tests/files/op7.gno index c92a567110d..8d5fef1607a 100644 --- a/gnovm/tests/files/op7.gno +++ b/gnovm/tests/files/op7.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/op7.gno:11: incompatible types in binary expression: err GTR invalidT +// main/files/op7.gno:11:5: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/pkgname1.gno b/gnovm/tests/files/pkgname1.gno index ab972295954..b53dacbe33b 100644 --- a/gnovm/tests/files/pkgname1.gno +++ b/gnovm/tests/files/pkgname1.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/pkgname1.gno:8: name bar not declared +// main/files/pkgname1.gno:8:19: name bar not declared diff --git a/gnovm/tests/files/print1.gno b/gnovm/tests/files/print1.gno index 606759a5c05..15c9e7bb278 100644 --- a/gnovm/tests/files/print1.gno +++ b/gnovm/tests/files/print1.gno @@ -6,4 +6,4 @@ func main() { } // Output: -// nil []string +// (nil []string) diff --git a/gnovm/tests/files/redeclaration9.gno b/gnovm/tests/files/redeclaration9.gno index 89a63683c13..f18b7e64379 100644 --- a/gnovm/tests/files/redeclaration9.gno +++ b/gnovm/tests/files/redeclaration9.gno @@ -11,4 +11,4 @@ func main() { } // Error: -// main/files/redeclaration9.gno:7: redeclaration of method a.method +// main/files/redeclaration9.gno:7:1: redeclaration of method a.method diff --git a/gnovm/tests/files/star_assign.gno b/gnovm/tests/files/star_assign.gno new file mode 100644 index 00000000000..e40fe2794ac --- /dev/null +++ b/gnovm/tests/files/star_assign.gno @@ -0,0 +1,54 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "gno.land/p/demo/ufmt" +) + +type A struct { + nums []int +} + +var ( + intPtr *int + strPtr *string + aPtr *A + concretePtr *int + concreteValue int +) + +func init() { + New() +} + +func main() { + Delta() + println(Values()) +} + +func New() { + intPtr = new(int) + strPtr = new(string) + aPtr = &A{} + concretePtr = &concreteValue +} + +func Delta() { + *intPtr++ + *strPtr += "hello" + *aPtr = A{nums: []int{8, 5, 8}} + *concretePtr = 100 +} + +func Values() string { + var results string + results += ufmt.Sprintf("%d, %s, %d, %d", *intPtr, *strPtr, *concretePtr, concreteValue) + for _, n := range aPtr.nums { + results += ufmt.Sprintf(", %d", n) + } + + return results +} + +// Output: +// 1, hello, 100, 100, 8, 5, 8 diff --git a/gnovm/tests/files/std0.gno b/gnovm/tests/files/std0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std0.gno rename to gnovm/tests/files/std0_stdlibs.gno diff --git a/gnovm/tests/files/std10.gno b/gnovm/tests/files/std10_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std10.gno rename to gnovm/tests/files/std10_stdlibs.gno diff --git a/gnovm/tests/files/std11.gno b/gnovm/tests/files/std11_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std11.gno rename to gnovm/tests/files/std11_stdlibs.gno diff --git a/gnovm/tests/files/std2.gno b/gnovm/tests/files/std2_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std2.gno rename to gnovm/tests/files/std2_stdlibs.gno diff --git a/gnovm/tests/files/std3.gno b/gnovm/tests/files/std3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std3.gno rename to gnovm/tests/files/std3_stdlibs.gno diff --git a/gnovm/tests/files/std4.gno b/gnovm/tests/files/std4_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std4.gno rename to gnovm/tests/files/std4_stdlibs.gno diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std5.gno rename to gnovm/tests/files/std5_stdlibs.gno diff --git a/gnovm/tests/files/std6.gno b/gnovm/tests/files/std6_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std6.gno rename to gnovm/tests/files/std6_stdlibs.gno diff --git a/gnovm/tests/files/std7.gno b/gnovm/tests/files/std7_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std7.gno rename to gnovm/tests/files/std7_stdlibs.gno diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std8.gno rename to gnovm/tests/files/std8_stdlibs.gno diff --git a/gnovm/tests/files/std9.gno b/gnovm/tests/files/std9_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std9.gno rename to gnovm/tests/files/std9_stdlibs.gno diff --git a/gnovm/tests/files/switch13.gno b/gnovm/tests/files/switch13.gno index bd32337620a..b2223c85256 100644 --- a/gnovm/tests/files/switch13.gno +++ b/gnovm/tests/files/switch13.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/switch13.gno:0#1: i is not a type +// main/files/switch13.gno:0:0: i is not a type diff --git a/gnovm/tests/files/switch19.gno b/gnovm/tests/files/switch19.gno index 71d4da222c0..4d769082647 100644 --- a/gnovm/tests/files/switch19.gno +++ b/gnovm/tests/files/switch19.gno @@ -48,4 +48,4 @@ func main() { } // Error: -// main/files/switch19.gno:34: duplicate type main.Bir in type switch +// main/files/switch19.gno:34:2: duplicate type main.Bir in type switch diff --git a/gnovm/tests/files/switch8.gno b/gnovm/tests/files/switch8.gno index 7f9269e5c40..c43c72582c0 100644 --- a/gnovm/tests/files/switch8.gno +++ b/gnovm/tests/files/switch8.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// 5:2: fallthrough statement out of place +// fallthrough statement out of place diff --git a/gnovm/tests/files/switch8b.gno b/gnovm/tests/files/switch8b.gno index b1704ec0313..cdf35caf784 100644 --- a/gnovm/tests/files/switch8b.gno +++ b/gnovm/tests/files/switch8b.gno @@ -12,4 +12,4 @@ func main() { } // Error: -// 10:3: cannot fallthrough final case in switch +// cannot fallthrough final case in switch diff --git a/gnovm/tests/files/switch8c.gno b/gnovm/tests/files/switch8c.gno index cd62e5ef874..6897b8a88fd 100644 --- a/gnovm/tests/files/switch8c.gno +++ b/gnovm/tests/files/switch8c.gno @@ -12,4 +12,4 @@ func main() { } // Error: -// 7:3: fallthrough statement out of place +// fallthrough statement out of place diff --git a/gnovm/tests/files/switch9.gno b/gnovm/tests/files/switch9.gno index 2d604438220..5f596de013a 100644 --- a/gnovm/tests/files/switch9.gno +++ b/gnovm/tests/files/switch9.gno @@ -13,4 +13,4 @@ func main() { } // Error: -// 9:3: cannot fallthrough in type switch +// cannot fallthrough in type switch diff --git a/gnovm/tests/files/time16_native.gno b/gnovm/tests/files/time16_native.gno new file mode 100644 index 00000000000..4010667b41c --- /dev/null +++ b/gnovm/tests/files/time16_native.gno @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + var a int64 = 2 + fmt.Println(time.Second * a) +} + +// Error: +// main/files/time16_native.gno:10:14: incompatible operands in binary expression: go:time.Duration MUL int64 diff --git a/gnovm/tests/files/type24b.gno b/gnovm/tests/files/type24b.gno index 54c1bb38df4..fa31104e4ff 100644 --- a/gnovm/tests/files/type24b.gno +++ b/gnovm/tests/files/type24b.gno @@ -46,4 +46,4 @@ 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)} +// *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/type30.gno b/gnovm/tests/files/type30.gno index cb21c245892..7ba9559d0ca 100644 --- a/gnovm/tests/files/type30.gno +++ b/gnovm/tests/files/type30.gno @@ -16,4 +16,4 @@ func main() { } // Error: -// main/files/type30.gno:15: cannot use main.String as string without explicit conversion +// main/files/type30.gno:15:10: cannot use main.String as string without explicit conversion diff --git a/gnovm/tests/files/type31.gno b/gnovm/tests/files/type31.gno index a80d07d70f2..5b666506e67 100644 --- a/gnovm/tests/files/type31.gno +++ b/gnovm/tests/files/type31.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/type31.gno:8: incompatible types in binary expression: x ADD y +// main/files/type31.gno:8:10: invalid operation: mismatched types string and main.String diff --git a/gnovm/tests/files/type32.gno b/gnovm/tests/files/type32.gno index b679543cf9f..d3462f29157 100644 --- a/gnovm/tests/files/type32.gno +++ b/gnovm/tests/files/type32.gno @@ -12,4 +12,4 @@ func main() { } // Error: -// main/files/type32.gno:9#1: incompatible types in binary expression: a + (const (":" string)) ADD b +// main/files/type32.gno:9:11: invalid operation: mismatched types string and main.S diff --git a/gnovm/tests/files/type37.gno b/gnovm/tests/files/type37.gno index 1bc157b44ff..0443672d496 100644 --- a/gnovm/tests/files/type37.gno +++ b/gnovm/tests/files/type37.gno @@ -17,4 +17,4 @@ func main() { } // Error: -// main/files/type37.gno:8: invalid receiver type main.Arr (base type is pointer type) +// main/files/type37.gno:8:1: invalid receiver type main.Arr (base type is pointer type) diff --git a/gnovm/tests/files/type37b.gno b/gnovm/tests/files/type37b.gno index aea1b445ca1..7b444e377eb 100644 --- a/gnovm/tests/files/type37b.gno +++ b/gnovm/tests/files/type37b.gno @@ -19,4 +19,4 @@ func main() { } // Error: -// main/files/type37b.gno:7: invalid receiver type **main.Integer (base type is pointer type) +// main/files/type37b.gno:7:1: invalid receiver type **main.Integer (base type is pointer type) diff --git a/gnovm/tests/files/type37d.gno b/gnovm/tests/files/type37d.gno index ada9541e64e..f8ff52d9754 100644 --- a/gnovm/tests/files/type37d.gno +++ b/gnovm/tests/files/type37d.gno @@ -13,4 +13,4 @@ func main() { } // Error: -// main/files/type37d.gno:7: invalid receiver type main.IntPtr (base type is pointer type) +// main/files/type37d.gno:7:1: invalid receiver type main.IntPtr (base type is pointer type) diff --git a/gnovm/tests/files/type37e.gno b/gnovm/tests/files/type37e.gno index a2537c9cb8d..8d8b51d2a20 100644 --- a/gnovm/tests/files/type37e.gno +++ b/gnovm/tests/files/type37e.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/type37e.gno:8: invalid receiver type main.Int2 (base type is pointer type) +// main/files/type37e.gno:8:1: invalid receiver type main.Int2 (base type is pointer type) diff --git a/gnovm/tests/files/type37f.gno b/gnovm/tests/files/type37f.gno index 7bffa748314..b448f914b3a 100644 --- a/gnovm/tests/files/type37f.gno +++ b/gnovm/tests/files/type37f.gno @@ -13,4 +13,4 @@ func main() { } // Error: -// main/files/type37f.gno:7: invalid receiver type *main.IntPtr (base type is pointer type) +// main/files/type37f.gno:7:1: invalid receiver type *main.IntPtr (base type is pointer type) diff --git a/gnovm/tests/files/type39.gno b/gnovm/tests/files/type39.gno index aebcc226385..7111d146ba2 100644 --- a/gnovm/tests/files/type39.gno +++ b/gnovm/tests/files/type39.gno @@ -19,4 +19,4 @@ func main() { } // Error: -// main/files/type39.gno:7: invalid receiver type main.foo (base type is interface type) +// main/files/type39.gno:7:1: invalid receiver type main.foo (base type is interface type) diff --git a/gnovm/tests/files/type39a.gno b/gnovm/tests/files/type39a.gno index 06f41897f93..fdfd6df8bb1 100644 --- a/gnovm/tests/files/type39a.gno +++ b/gnovm/tests/files/type39a.gno @@ -21,4 +21,4 @@ func main() { } // Error: -// main/files/type39a.gno:9: invalid receiver type main.FF (base type is interface type) +// main/files/type39a.gno:9:1: invalid receiver type main.FF (base type is interface type) diff --git a/gnovm/tests/files/type39b.gno b/gnovm/tests/files/type39b.gno index dbf5312a825..4c95044fbd1 100644 --- a/gnovm/tests/files/type39b.gno +++ b/gnovm/tests/files/type39b.gno @@ -19,4 +19,4 @@ func main() { } // Error: -// main/files/type39b.gno:7: invalid receiver type *main.foo (base type is interface type) +// main/files/type39b.gno:7:1: invalid receiver type *main.foo (base type is interface type) diff --git a/gnovm/tests/files/type_alias.gno b/gnovm/tests/files/type_alias.gno new file mode 100644 index 00000000000..e95c54126ec --- /dev/null +++ b/gnovm/tests/files/type_alias.gno @@ -0,0 +1,12 @@ +// PKGPATH: gno.land/r/type_alias_test +package type_alias_test + +import "gno.land/p/demo/uassert" + +type TestingT = uassert.TestingT + +func main() { + println(TestingT) +} + +// No need for output; not panicking is passing. diff --git a/gnovm/tests/files/typeassert1.gno b/gnovm/tests/files/typeassert1.gno new file mode 100644 index 00000000000..041034e4bd0 --- /dev/null +++ b/gnovm/tests/files/typeassert1.gno @@ -0,0 +1,13 @@ +package main + +type A interface { + Do(s string) +} + +func main() { + var a A + _ = a.(A) +} + +// Error: +// interface conversion: interface is nil, not main.A diff --git a/gnovm/tests/files/typeassert2.gno b/gnovm/tests/files/typeassert2.gno new file mode 100644 index 00000000000..a9791fa0038 --- /dev/null +++ b/gnovm/tests/files/typeassert2.gno @@ -0,0 +1,32 @@ +package main + +type ex int + +func (ex) Error() string { return "" } + +type A interface { + Do(s string) +} + +func main() { + defer func() { + e := recover() + if _, ok := e.(ex); ok { + println("wat") + } else { + println("ok") + } + }() + defer func() { + e := recover() + if _, ok := e.(A); ok { + println("wat") + } else { + println("ok") + } + }() +} + +// Output: +// ok +// ok diff --git a/gnovm/tests/files/typeassert2a.gno b/gnovm/tests/files/typeassert2a.gno new file mode 100644 index 00000000000..0441bf83437 --- /dev/null +++ b/gnovm/tests/files/typeassert2a.gno @@ -0,0 +1,15 @@ +package main + +type A interface { + Do(s string) +} + +func main() { + defer func() { + e := recover() + _ = e.(A) + }() +} + +// Error: +// interface conversion: interface is nil, not main.A diff --git a/gnovm/tests/files/typeassert3.gno b/gnovm/tests/files/typeassert3.gno new file mode 100644 index 00000000000..41f97bf3488 --- /dev/null +++ b/gnovm/tests/files/typeassert3.gno @@ -0,0 +1,15 @@ +package main + +type ex int + +func (ex) Error() string { return "" } + +func main() { + defer func() { + r := _.(ex) + println(r) + }() +} + +// Error: +// main/files/typeassert3.gno:9:8: cannot use _ as value or type diff --git a/gnovm/tests/files/typeassert4.gno b/gnovm/tests/files/typeassert4.gno new file mode 100644 index 00000000000..a137d3833ee --- /dev/null +++ b/gnovm/tests/files/typeassert4.gno @@ -0,0 +1,58 @@ +package main + +type Setter interface { + Set(string) +} + +type SetterClone interface { + Set(string) +} + +type ValueSetter struct { + value string +} + +func (s *ValueSetter) Set(value string) { + s.value = value +} + +func cmpSetter(i interface{}) { + if _, ok := i.(Setter); ok { + println("ok") + } else { + println("not ok") + } +} + +func main() { + var ( + i interface{} + setter Setter + setterClone SetterClone + valueSetter ValueSetter + valueSetterPtr *ValueSetter + ) + + cmpSetter(i) + + i = setter + cmpSetter(i) + + setterClone = valueSetterPtr + setter = setterClone + i = setter + cmpSetter(i) + + i = valueSetter + cmpSetter(i) + + i = valueSetterPtr + cmpSetter(i) +} + +// Output: +// not ok +// not ok +// ok +// not ok +// ok diff --git a/gnovm/tests/files/typeassert4a.gno b/gnovm/tests/files/typeassert4a.gno new file mode 100644 index 00000000000..09e9ae06aa1 --- /dev/null +++ b/gnovm/tests/files/typeassert4a.gno @@ -0,0 +1,62 @@ +package main + +type Setter interface { + Set(string) +} + +type SetterClone interface { + Set(string) +} + +type ValueSetter struct { + value string +} + +func (s *ValueSetter) Set(value string) { + s.value = value +} + +func cmpSetter(i interface{}) { + defer func() { + if r := recover(); r != nil { + println(r) + } else { + println("ok") + } + }() + + _ = i.(Setter) +} + +func main() { + var ( + i interface{} + setter Setter + setterClone SetterClone + valueSetter ValueSetter + valueSetterPtr *ValueSetter + ) + + cmpSetter(i) + + i = setter + cmpSetter(i) + + setterClone = valueSetterPtr + setter = setterClone + i = setter + cmpSetter(i) + + i = valueSetter + cmpSetter(i) + + i = valueSetterPtr + cmpSetter(i) +} + +// Output: +// 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) +// ok diff --git a/gnovm/tests/files/typeassert5.gno b/gnovm/tests/files/typeassert5.gno new file mode 100644 index 00000000000..f548de6c0e1 --- /dev/null +++ b/gnovm/tests/files/typeassert5.gno @@ -0,0 +1,30 @@ +package main + +type A interface { + Do(i int) +} + +type B interface { + Do(i int) +} + +type C struct{} + +func (c C) Do(i int) {} + +func AcceptA(a A) { + AcceptB(a) +} + +func AcceptB(b B) { + if _, ok := b.(A); ok { + println("ok") + } +} + +func main() { + AcceptA(C{}) +} + +// Output: +// ok diff --git a/gnovm/tests/files/typeassert5a.gno b/gnovm/tests/files/typeassert5a.gno new file mode 100644 index 00000000000..a4f720d65c6 --- /dev/null +++ b/gnovm/tests/files/typeassert5a.gno @@ -0,0 +1,29 @@ +package main + +type A interface { + Do(i int) +} + +type B interface { + Do(i int) +} + +type C struct{} + +func (c C) Do(i int) {} + +func AcceptA(a A) { + AcceptB(a) +} + +func AcceptB(b B) { + _ = b.(A) + println("ok") +} + +func main() { + AcceptA(C{}) +} + +// Output: +// ok diff --git a/gnovm/tests/files/typeassert6.gno b/gnovm/tests/files/typeassert6.gno new file mode 100644 index 00000000000..5faa392a703 --- /dev/null +++ b/gnovm/tests/files/typeassert6.gno @@ -0,0 +1,25 @@ +package main + +type A interface { + Do(s string) +} + +func main() { + var v interface{} + v = 9 + + if _, ok := v.(A); !ok { + println(v) + } + + vp := new(int) + *vp = 99 + v = vp + if _, ok := v.(A); !ok { + println(*(v.(*int))) + } +} + +// Output: +// 9 +// 99 diff --git a/gnovm/tests/files/typeassert6a.gno b/gnovm/tests/files/typeassert6a.gno new file mode 100644 index 00000000000..e55f077c334 --- /dev/null +++ b/gnovm/tests/files/typeassert6a.gno @@ -0,0 +1,40 @@ +package main + +type A interface { + Do(s string) +} + +func test1() { + defer func() { + if r := recover(); r != nil { + println(r) + } + }() + + var v interface{} + v = 9 + _ = v.(A) +} + +func test2() { + defer func() { + if r := recover(); r != nil { + println(r) + } + }() + + var v interface{} + vp := new(int) + *vp = 99 + v = vp + _ = v.(A) +} + +func main() { + test1() + test2() +} + +// Output: +// 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_native.gno b/gnovm/tests/files/typeassert7_native.gno new file mode 100644 index 00000000000..ed79ecf2e2e --- /dev/null +++ b/gnovm/tests/files/typeassert7_native.gno @@ -0,0 +1,44 @@ +package main + +import ( + "bytes" + "io" +) + +func main() { + { + var v interface{} + var r io.Reader + r = bytes.NewBuffer([]byte("hello")) + v = r + if _, ok := v.(io.Reader); ok { + println("ok") + } else { + println("not ok") + } + } + { + var v interface{} + var r io.Reader + v = r + if _, ok := v.(io.Reader); ok { + println("ok") + } else { + println("not ok") + } + } + { + var v interface{} + v = bytes.NewBuffer([]byte("hello")) + if _, ok := v.(io.Reader); ok { + println("ok") + } else { + println("not ok") + } + } +} + +// Output: +// ok +// not ok +// ok diff --git a/gnovm/tests/files/typeassert7a_native.gno b/gnovm/tests/files/typeassert7a_native.gno new file mode 100644 index 00000000000..cafb27b6a6b --- /dev/null +++ b/gnovm/tests/files/typeassert7a_native.gno @@ -0,0 +1,43 @@ +package main + +import ( + "bytes" + "io" +) + +func testImpl(v interface{}) { + defer func() { + if r := recover(); r != nil { + println(r) + } + }() + + _ = v.(io.Reader) + println("ok") +} + +func main() { + { + var v interface{} + var r io.Reader + r = bytes.NewBuffer([]byte("hello")) + v = r + testImpl(v) + } + { + var v interface{} + var r io.Reader + v = r + testImpl(v) + } + { + var v interface{} + v = bytes.NewBuffer([]byte("hello")) + testImpl(v) + } +} + +// Output: +// ok +// interface conversion: interface is nil, not gonative{io.Reader} +// ok diff --git a/gnovm/tests/files/typeassert8.gno b/gnovm/tests/files/typeassert8.gno new file mode 100644 index 00000000000..737f2e5d98f --- /dev/null +++ b/gnovm/tests/files/typeassert8.gno @@ -0,0 +1,18 @@ +package main + +type ex int + +func (ex) Error() string { return "" } + +type i interface { + Error() string +} + +func main() { + r := []int(nil) + e := r.(ex) + println(e) +} + +// Error: +// main/files/typeassert8.gno:13:7: invalid operation: r (variable of type []int) is not an interface diff --git a/gnovm/tests/files/typeassert8a.gno b/gnovm/tests/files/typeassert8a.gno new file mode 100644 index 00000000000..2b163e83ddc --- /dev/null +++ b/gnovm/tests/files/typeassert8a.gno @@ -0,0 +1,18 @@ +package main + +type ex int + +func (ex) Error() string { return "" } + +type i interface { + Error() string +} + +func main() { + r := []int(nil) + e, ok := r.(ex) + println(e, ok) +} + +// Error: +// main/files/typeassert8a.gno:13:11: invalid operation: r (variable of type []int) is not an interface diff --git a/gnovm/tests/files/typeassert9.gno b/gnovm/tests/files/typeassert9.gno new file mode 100644 index 00000000000..d9d5bad55af --- /dev/null +++ b/gnovm/tests/files/typeassert9.gno @@ -0,0 +1,20 @@ +package main + +// First interface +type Reader interface { + Read() string +} + +// Second interface +type Writer interface { + Write() string +} + +func main() { + var reader Reader + + _ = reader.(Writer) +} + +// Error: +// interface conversion: interface is nil, not main.Writer diff --git a/gnovm/tests/files/typeassert9a.gno b/gnovm/tests/files/typeassert9a.gno new file mode 100644 index 00000000000..8de8aa773b1 --- /dev/null +++ b/gnovm/tests/files/typeassert9a.gno @@ -0,0 +1,19 @@ +package main + +// First interface +type Reader interface { + Read(int) string +} + +type csvReader struct{} +func (r*csvReader) Read(string) string{ + return "" +} + + +func main() { + var csvReader Reader = &csvReader{} +} + +// Error: +// main/files/typeassert9a.gno:15:6: *main.csvReader does not implement main.Reader (wrong type for method Read) diff --git a/gnovm/tests/files/types/add_a0.gno b/gnovm/tests/files/types/add_a0.gno new file mode 100644 index 00000000000..01b080d9d55 --- /dev/null +++ b/gnovm/tests/files/types/add_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) + int8(1)) +} + +// Error: +// main/files/types/add_a0.gno:5:10: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/add_a1.gno b/gnovm/tests/files/types/add_a1.gno new file mode 100644 index 00000000000..c7ea22d04fd --- /dev/null +++ b/gnovm/tests/files/types/add_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) + Error2(0)) +} + +// Error: +// main/files/types/add_a1.gno:21:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_assign_a0.gno b/gnovm/tests/files/types/add_assign_a0.gno new file mode 100644 index 00000000000..9c13b8815ef --- /dev/null +++ b/gnovm/tests/files/types/add_assign_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + int(0) += int8(1) +} + +// Error: +// main/files/types/add_assign_a0.gno:5:2: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/add_assign_a1.gno b/gnovm/tests/files/types/add_assign_a1.gno new file mode 100644 index 00000000000..1e844a9f6ee --- /dev/null +++ b/gnovm/tests/files/types/add_assign_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + Error1(0) += Error2(0) +} + +// Error: +// main/files/types/add_assign_a1.gno:21:2: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_assign_a_01.gno b/gnovm/tests/files/types/add_assign_a_01.gno new file mode 100644 index 00000000000..4641ef8b1a9 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_a_01.gno @@ -0,0 +1,12 @@ +package main + +func main() { + a := 1 + b := 1 + a, b += 1, 1 + println(a) + println(b) +} + +// Error: +// main/files/types/add_assign_a_01.gno:6:2: assignment operator += requires only one expression on lhs and rhs diff --git a/gnovm/tests/files/types/add_assign_b0.gno b/gnovm/tests/files/types/add_assign_b0.gno new file mode 100644 index 00000000000..e335b11014e --- /dev/null +++ b/gnovm/tests/files/types/add_assign_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + r := 1 + r += Error(1) + println(r) +} + +// Error: +// main/files/types/add_assign_b0.gno:8:2: invalid operation: mismatched types int and main.Error diff --git a/gnovm/tests/files/types/add_assign_b1.gno b/gnovm/tests/files/types/add_assign_b1.gno new file mode 100644 index 00000000000..2e702256f29 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_b1.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + r := Error(1) + r += 1 + println(r) +} + +// Output: +// (2 main.Error) diff --git a/gnovm/tests/files/types/add_assign_b2.gno b/gnovm/tests/files/types/add_assign_b2.gno new file mode 100644 index 00000000000..87b09754046 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_b2.gno @@ -0,0 +1,10 @@ +package main + +func main() { + r := 1 + r += 'a' + println(r) +} + +// Output: +// 98 diff --git a/gnovm/tests/files/types/add_assign_d0.gno b/gnovm/tests/files/types/add_assign_d0.gno new file mode 100644 index 00000000000..f3da2497a84 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_d0.gno @@ -0,0 +1,12 @@ +package main + +// both untyped const +// TODO: dec value representation, and this should happen in process stage!!! +func main() { + r := 1.0 + r += 1 + println(r) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/add_assign_e.gno b/gnovm/tests/files/types/add_assign_e.gno new file mode 100644 index 00000000000..3a72f9711be --- /dev/null +++ b/gnovm/tests/files/types/add_assign_e.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func main() { + var e1 Error1 = Error1(0) + var e2 int64 = 1 + e1 += Error1(e2) + println(e1) +} + +// Output: +// error: 1 diff --git a/gnovm/tests/files/types/add_assign_e0.gno b/gnovm/tests/files/types/add_assign_e0.gno new file mode 100644 index 00000000000..4319f1e828f --- /dev/null +++ b/gnovm/tests/files/types/add_assign_e0.gno @@ -0,0 +1,28 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + e1 += e2 + println(e1) +} + +// Error: +// main/files/types/add_assign_e0.gno:23:2: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_assign_e1.gno b/gnovm/tests/files/types/add_assign_e1.gno new file mode 100644 index 00000000000..3657cf2872d --- /dev/null +++ b/gnovm/tests/files/types/add_assign_e1.gno @@ -0,0 +1,20 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func main() { + var e1 Error1 = Error1(0) + e1 += 1 + println(e1) +} + +// Output: +// error: 1 diff --git a/gnovm/tests/files/types/add_assign_f0_stdlibs.gno b/gnovm/tests/files/types/add_assign_f0_stdlibs.gno new file mode 100644 index 00000000000..67c6777d085 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f0_stdlibs.gno @@ -0,0 +1,25 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + r := 1 + r += errCmp + println(r) +} + +// Error: +// main/files/types/add_assign_f0_stdlibs.gno:20:2: invalid operation: mismatched types int and .uverse.error diff --git a/gnovm/tests/files/types/add_assign_f1_stdlibs.gno b/gnovm/tests/files/types/add_assign_f1_stdlibs.gno new file mode 100644 index 00000000000..d83a66359c9 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f1_stdlibs.gno @@ -0,0 +1,28 @@ +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: +// 1. base type of left is int64, op is legal; +// 2. while RHS is interface kind, and can be converted to left +func main() { + r := Error(0) + r += errCmp // in case of this, should panic mismatch on operand, except RHS is untyped + //println(r) + // println(Error(0) == errCmp) // Note: this is different with += + +} + +// Error: +// main/files/types/add_assign_f1_stdlibs.gno:21:2: invalid operation: mismatched types main.Error and .uverse.error diff --git a/gnovm/tests/files/types/add_assign_f2_stdlibs.gno b/gnovm/tests/files/types/add_assign_f2_stdlibs.gno new file mode 100644 index 00000000000..8be6b3cfb7b --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f2_stdlibs.gno @@ -0,0 +1,25 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + r := Error(0) + errCmp += r + println(errCmp) +} + +// Error: +// main/files/types/add_assign_f2_stdlibs.gno:20:2: operator += not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_assign_f3.gno b/gnovm/tests/files/types/add_assign_f3.gno new file mode 100644 index 00000000000..447cfdd7501 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f3.gno @@ -0,0 +1,32 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + e1 += e2 + println(e1) +} + +// Error: +// main/files/types/add_assign_f3.gno:27:2: operator += not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_assign_f4.gno b/gnovm/tests/files/types/add_assign_f4.gno new file mode 100644 index 00000000000..bb4083e8935 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a += int(1) + println(a) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/add_b0.gno b/gnovm/tests/files/types/add_b0.gno new file mode 100644 index 00000000000..3790f9d7134 --- /dev/null +++ b/gnovm/tests/files/types/add_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 + Error(1)) + println(Error(1) + 1) +} + +// Output: +// (2 main.Error) +// (2 main.Error) diff --git a/gnovm/tests/files/types/add_b1.gno b/gnovm/tests/files/types/add_b1.gno new file mode 100644 index 00000000000..854ecaa9770 --- /dev/null +++ b/gnovm/tests/files/types/add_b1.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// one untyped const, one typed const +func main() { + println(1 + Error(1)) + println(Error(1) + 1) +} + +// Output: +// error: 2 +// error: 2 diff --git a/gnovm/tests/files/types/add_b2.gno b/gnovm/tests/files/types/add_b2.gno new file mode 100644 index 00000000000..fe2a9b872cc --- /dev/null +++ b/gnovm/tests/files/types/add_b2.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 + "a") +} + +// Error: +// main/files/types/add_b2.gno:5:10: cannot use untyped Bigint as StringKind diff --git a/gnovm/tests/files/types/add_b3.gno b/gnovm/tests/files/types/add_b3.gno new file mode 100644 index 00000000000..c8a94c5b6ab --- /dev/null +++ b/gnovm/tests/files/types/add_b3.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println("b" + "a") +} + +// Output: +// ba diff --git a/gnovm/tests/files/types/add_d0.gno b/gnovm/tests/files/types/add_d0.gno new file mode 100644 index 00000000000..3f0d9548fff --- /dev/null +++ b/gnovm/tests/files/types/add_d0.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// untyped bigint to untyped bigdec +// TODO: dec value representation +func main() { + println(1.0 + 1) + println(1.0 + 0) +} + +// Output: +// 2 +// 1 diff --git a/gnovm/tests/files/types/add_d1.gno b/gnovm/tests/files/types/add_d1.gno new file mode 100644 index 00000000000..7bf7c26565a --- /dev/null +++ b/gnovm/tests/files/types/add_d1.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println('a' + 'b') +} + +// Output: +// 195 diff --git a/gnovm/tests/files/types/add_d2.gno b/gnovm/tests/files/types/add_d2.gno new file mode 100644 index 00000000000..7ea4924a594 --- /dev/null +++ b/gnovm/tests/files/types/add_d2.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// TODO: dec value representation +var r rune + +func main() { + r = 'a' + println(r + 'b') +} + +// Output: +// 195 diff --git a/gnovm/tests/files/types/add_d3.gno b/gnovm/tests/files/types/add_d3.gno new file mode 100644 index 00000000000..b130fc98b66 --- /dev/null +++ b/gnovm/tests/files/types/add_d3.gno @@ -0,0 +1,15 @@ +package main + +// both untyped const +// TODO: dec value representation +var r1 rune +var r2 rune + +func main() { + r1 = 'a' + r2 = 'b' + println(r1 + r2) +} + +// Output: +// 195 diff --git a/gnovm/tests/files/types/add_d4.gno b/gnovm/tests/files/types/add_d4.gno new file mode 100644 index 00000000000..a1ff8e09ae7 --- /dev/null +++ b/gnovm/tests/files/types/add_d4.gno @@ -0,0 +1,11 @@ +package main + +var a int +var b interface{} + +func main() { + println(b + a) +} + +// Error: +// main/files/types/add_d4.gno:7:10: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_e0.gno b/gnovm/tests/files/types/add_e0.gno new file mode 100644 index 00000000000..12e20f5a8e0 --- /dev/null +++ b/gnovm/tests/files/types/add_e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 + e2) +} + +// Error: +// main/files/types/add_e0.gno:23:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_f0_stdlibs.gno b/gnovm/tests/files/types/add_f0_stdlibs.gno new file mode 100644 index 00000000000..33e0346d44f --- /dev/null +++ b/gnovm/tests/files/types/add_f0_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(1 + errCmp) +} + +// Error: +// main/files/types/add_f0_stdlibs.gno:19:10: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_f1_stdlibs.gno b/gnovm/tests/files/types/add_f1_stdlibs.gno new file mode 100644 index 00000000000..e46d67e93d7 --- /dev/null +++ b/gnovm/tests/files/types/add_f1_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(Error(0) + errCmp) +} + +// Error: +// main/files/types/add_f1_stdlibs.gno:19:10: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_f2.gno b/gnovm/tests/files/types/add_f2.gno new file mode 100644 index 00000000000..276610ac6c9 --- /dev/null +++ b/gnovm/tests/files/types/add_f2.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 + e2) +} + +// Error: +// main/files/types/add_f2.gno:27:10: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/and_a0.gno b/gnovm/tests/files/types/and_a0.gno new file mode 100644 index 00000000000..8be9a325f7a --- /dev/null +++ b/gnovm/tests/files/types/and_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(0) & int8(1)) +} + +// Error: +// main/files/types/and_a0.gno:5:10: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/and_a1.gno b/gnovm/tests/files/types/and_a1.gno new file mode 100644 index 00000000000..e7f1fe1d37c --- /dev/null +++ b/gnovm/tests/files/types/and_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) & Error2(0)) +} + +// Error: +// main/files/types/and_a1.gno:21:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/and_b0.gno b/gnovm/tests/files/types/and_b0.gno new file mode 100644 index 00000000000..525796320e6 --- /dev/null +++ b/gnovm/tests/files/types/and_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 & Error(1)) + println(Error(1) & 1) +} + +// Output: +// (1 main.Error) +// (1 main.Error) diff --git a/gnovm/tests/files/types/and_b1.gno b/gnovm/tests/files/types/and_b1.gno new file mode 100644 index 00000000000..dc21fc3956b --- /dev/null +++ b/gnovm/tests/files/types/and_b1.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// one untyped const, one typed const +func main() { + println(1 & Error(1)) + println(Error(1) & 1) +} + +// Output: +// error: 1 +// error: 1 diff --git a/gnovm/tests/files/types/and_b2.gno b/gnovm/tests/files/types/and_b2.gno new file mode 100644 index 00000000000..bec070e9300 --- /dev/null +++ b/gnovm/tests/files/types/and_b2.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 & "a") +} + +// Error: +// main/files/types/and_b2.gno:5:10: operator & not defined on: StringKind diff --git a/gnovm/tests/files/types/and_b3.gno b/gnovm/tests/files/types/and_b3.gno new file mode 100644 index 00000000000..1ca8fab934e --- /dev/null +++ b/gnovm/tests/files/types/and_b3.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println("b" & "a") +} + +// Error: +// main/files/types/and_b3.gno:5:10: operator & not defined on: StringKind diff --git a/gnovm/tests/files/types/and_b4.gno b/gnovm/tests/files/types/and_b4.gno new file mode 100644 index 00000000000..725714eeafd --- /dev/null +++ b/gnovm/tests/files/types/and_b4.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 & 'a') +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/and_d0.gno b/gnovm/tests/files/types/and_d0.gno new file mode 100644 index 00000000000..5246f447f46 --- /dev/null +++ b/gnovm/tests/files/types/and_d0.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation, and this should happen in process stage!!! +func main() { + println(1.0 & 1) +} + +// Error: +// main/files/types/and_d0.gno:6:10: operator & not defined on: BigdecKind diff --git a/gnovm/tests/files/types/and_d1.gno b/gnovm/tests/files/types/and_d1.gno new file mode 100644 index 00000000000..232fff4d385 --- /dev/null +++ b/gnovm/tests/files/types/and_d1.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println('a' & 'b') +} + +// Output: +// 96 diff --git a/gnovm/tests/files/types/and_d2.gno b/gnovm/tests/files/types/and_d2.gno new file mode 100644 index 00000000000..f5c2660bafc --- /dev/null +++ b/gnovm/tests/files/types/and_d2.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// TODO: dec value representation +var r rune + +func main() { + r = 'a' + println(r & 'b') +} + +// Output: +// 96 diff --git a/gnovm/tests/files/types/and_d3.gno b/gnovm/tests/files/types/and_d3.gno new file mode 100644 index 00000000000..f5cb8075f24 --- /dev/null +++ b/gnovm/tests/files/types/and_d3.gno @@ -0,0 +1,15 @@ +package main + +// both untyped const +// TODO: dec value representation +var r1 rune +var r2 rune + +func main() { + r1 = 'a' + r2 = 'b' + println(r1 & r2) +} + +// Output: +// 96 diff --git a/gnovm/tests/files/types/and_d4.gno b/gnovm/tests/files/types/and_d4.gno new file mode 100644 index 00000000000..461a55aae45 --- /dev/null +++ b/gnovm/tests/files/types/and_d4.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println(1.0 & 0) +} + +// Error: +// main/files/types/and_d4.gno:6:10: operator & not defined on: BigdecKind diff --git a/gnovm/tests/files/types/and_e0.gno b/gnovm/tests/files/types/and_e0.gno new file mode 100644 index 00000000000..d3cb8aefe73 --- /dev/null +++ b/gnovm/tests/files/types/and_e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 & e2) +} + +// Error: +// main/files/types/and_e0.gno:23:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/and_f0_stdlibs.gno b/gnovm/tests/files/types/and_f0_stdlibs.gno new file mode 100644 index 00000000000..e80f69332a8 --- /dev/null +++ b/gnovm/tests/files/types/and_f0_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(1 & errCmp) +} + +// Error: +// main/files/types/and_f0_stdlibs.gno:19:10: operator & not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/and_f1_stdlibs.gno b/gnovm/tests/files/types/and_f1_stdlibs.gno new file mode 100644 index 00000000000..42a6aa4b466 --- /dev/null +++ b/gnovm/tests/files/types/and_f1_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(Error(0) & errCmp) +} + +// Error: +// main/files/types/and_f1_stdlibs.gno:19:10: operator & not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/and_f2.gno b/gnovm/tests/files/types/and_f2.gno new file mode 100644 index 00000000000..35bdebad9bb --- /dev/null +++ b/gnovm/tests/files/types/and_f2.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 & e2) +} + +// Error: +// main/files/types/and_f2.gno:27:10: operator & not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/assign_call.gno b/gnovm/tests/files/types/assign_call.gno new file mode 100644 index 00000000000..e62abe45485 --- /dev/null +++ b/gnovm/tests/files/types/assign_call.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, float32) { + return 1, 1.0 +} + +func main() { + var s, s1 string + s, s1 = foo() + println(s) +} + +// Error: +// main/files/types/assign_call.gno:9:2: cannot use int as string diff --git a/gnovm/tests/files/types/assign_index.gno b/gnovm/tests/files/types/assign_index.gno new file mode 100644 index 00000000000..8431e5a58ca --- /dev/null +++ b/gnovm/tests/files/types/assign_index.gno @@ -0,0 +1,14 @@ +package main + +func main() { + m := map[string]int{"a": 1, "b": 2, "c": 3} + + var s string + var ok bool + s, ok = m["a"] + println(s) + println(ok) +} + +// Error: +// main/files/types/assign_index.gno:8:2: cannot use int as string diff --git a/gnovm/tests/files/types/assign_index_a.gno b/gnovm/tests/files/types/assign_index_a.gno new file mode 100644 index 00000000000..5989fe286b9 --- /dev/null +++ b/gnovm/tests/files/types/assign_index_a.gno @@ -0,0 +1,12 @@ +package main + +func main() { + var s string + var ok bool + s, ok = map[string]int{"a": 1, "b": 2, "c": 3}["a"] + println(s) + println(ok) +} + +// Error: +// main/files/types/assign_index_a.gno:6:2: cannot use int as string diff --git a/gnovm/tests/files/types/assign_index_b.gno b/gnovm/tests/files/types/assign_index_b.gno new file mode 100644 index 00000000000..5991891a71e --- /dev/null +++ b/gnovm/tests/files/types/assign_index_b.gno @@ -0,0 +1,14 @@ +package main + +func main() { + m := map[string]int{"a": 1, "b": 2, "c": 3} + + var s int + var ok int + s, ok = m["a"] + println(s) + println(ok) +} + +// Error: +// main/files/types/assign_index_b.gno:8:2: want bool type got int diff --git a/gnovm/tests/files/types/assign_index_c.gno b/gnovm/tests/files/types/assign_index_c.gno new file mode 100644 index 00000000000..aaf0feb8c03 --- /dev/null +++ b/gnovm/tests/files/types/assign_index_c.gno @@ -0,0 +1,12 @@ +package main + +func main() { + s := []int{1, 2, 3} + + var s1 string + s1 = s[0] + println(s) +} + +// Error: +// main/files/types/assign_index_c.gno:7:2: cannot use int as string diff --git a/gnovm/tests/files/types/assign_literal.gno b/gnovm/tests/files/types/assign_literal.gno new file mode 100644 index 00000000000..636389194cf --- /dev/null +++ b/gnovm/tests/files/types/assign_literal.gno @@ -0,0 +1,8 @@ +package main + +func main() { + 1 = 6 +} + +// Error: +// main/files/types/assign_literal.gno:4:2: cannot assign to (const (1 bigint)) diff --git a/gnovm/tests/files/types/assign_literal10.gno b/gnovm/tests/files/types/assign_literal10.gno new file mode 100644 index 00000000000..d7b29f91aec --- /dev/null +++ b/gnovm/tests/files/types/assign_literal10.gno @@ -0,0 +1,21 @@ +package main + +type foo struct { + a int +} + +var n int + +func (f foo) add() *int { return &n } + +func main() { + s := &foo{} + *(s.add()) = 1 + println((*s).a) + + println(n) +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/types/assign_literal10a.gno b/gnovm/tests/files/types/assign_literal10a.gno new file mode 100644 index 00000000000..a0945951eaf --- /dev/null +++ b/gnovm/tests/files/types/assign_literal10a.gno @@ -0,0 +1,20 @@ +package main + +type foo struct { + a int +} + +var n int + +func (f foo) add() *int { return &n } + +func main() { + s := &foo{} + s.add() = 1 + println((*s).a) + + println(n) +} + +// Error: +// main/files/types/assign_literal10a.gno:13:2: cannot assign to s.add() diff --git a/gnovm/tests/files/types/assign_literal11.gno b/gnovm/tests/files/types/assign_literal11.gno new file mode 100644 index 00000000000..851fe74593d --- /dev/null +++ b/gnovm/tests/files/types/assign_literal11.gno @@ -0,0 +1,11 @@ +package main + +const Pi = 3.14 + +func main() { + Pi = 3.14159 + println(Pi) +} + +// Error: +// main/files/types/assign_literal11.gno:6:2: cannot assign to (const (3.14 bigdec)) diff --git a/gnovm/tests/files/types/assign_literal2.gno b/gnovm/tests/files/types/assign_literal2.gno new file mode 100644 index 00000000000..dd48a156928 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a int + a, 2 = 6, 6 +} + +// Error: +// main/files/types/assign_literal2.gno:5:2: cannot assign to (const (2 bigint)) diff --git a/gnovm/tests/files/types/assign_literal2_a.gno b/gnovm/tests/files/types/assign_literal2_a.gno new file mode 100644 index 00000000000..f66868ab842 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal2_a.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a int + a, 2 := 6, 6 +} + +// Error: +// files/types/assign_literal2_a.gno:5:2: no new variables on left side of := \ No newline at end of file diff --git a/gnovm/tests/files/types/assign_literal3.gno b/gnovm/tests/files/types/assign_literal3.gno new file mode 100644 index 00000000000..e4a4441853d --- /dev/null +++ b/gnovm/tests/files/types/assign_literal3.gno @@ -0,0 +1,8 @@ +package main + +func main() { + true = false +} + +// Error: +// main/files/types/assign_literal3.gno:4:2: cannot assign to (const (true bool)) diff --git a/gnovm/tests/files/types/assign_literal4.gno b/gnovm/tests/files/types/assign_literal4.gno new file mode 100644 index 00000000000..95d6684aaaf --- /dev/null +++ b/gnovm/tests/files/types/assign_literal4.gno @@ -0,0 +1,8 @@ +package main + +func main() { + []int{1, 2} = []int{3, 4} +} + +// Error: +// main/files/types/assign_literal4.gno:4:2: cannot assign to [](const-type int){(const (1 int)), (const (2 int))} diff --git a/gnovm/tests/files/types/assign_literal4_a.gno b/gnovm/tests/files/types/assign_literal4_a.gno new file mode 100644 index 00000000000..054ae227b32 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal4_a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + []int{1, 2} := []int{3, 4} +} + +// Error: +// files/types/assign_literal4_a.gno:4:2: no new variables on left side of := \ No newline at end of file diff --git a/gnovm/tests/files/types/assign_literal5.gno b/gnovm/tests/files/types/assign_literal5.gno new file mode 100644 index 00000000000..b63a6cb9923 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal5.gno @@ -0,0 +1,8 @@ +package main + +func main() { + map[string]int{"a": 1, "b": 2} = map[string]int{"a": 1, "b": 2} +} + +// Error: +// main/files/types/assign_literal5.gno:4:2: cannot assign to map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))} diff --git a/gnovm/tests/files/types/assign_literal6.gno b/gnovm/tests/files/types/assign_literal6.gno new file mode 100644 index 00000000000..7e12e18744a --- /dev/null +++ b/gnovm/tests/files/types/assign_literal6.gno @@ -0,0 +1,8 @@ +package main + +func main() { + 1 + 2 = 6 +} + +// Error: +// main/files/types/assign_literal6.gno:4:2: cannot assign to (const (3 bigint)) diff --git a/gnovm/tests/files/types/assign_literal7.gno b/gnovm/tests/files/types/assign_literal7.gno new file mode 100644 index 00000000000..56308cdbbfc --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7.gno @@ -0,0 +1,10 @@ +package main + +func main() { + s := []int{1, 2, 3} + s[0] = 0 + println(s) +} + +// Output: +// slice[(0 int),(2 int),(3 int)] diff --git a/gnovm/tests/files/types/assign_literal7a.gno b/gnovm/tests/files/types/assign_literal7a.gno new file mode 100644 index 00000000000..8be126c49de --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7a.gno @@ -0,0 +1,10 @@ +package main + +func main() { + m := map[string]int{"a": 1, "b": 2, "c": 3} + m["a"] = 0 + println(m) +} + +// Output: +// map{("a" string):(0 int),("b" string):(2 int),("c" string):(3 int)} diff --git a/gnovm/tests/files/types/assign_literal7b.gno b/gnovm/tests/files/types/assign_literal7b.gno new file mode 100644 index 00000000000..dc61467e07c --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7b.gno @@ -0,0 +1,10 @@ +package main + +func main() { + s := [2]int{} + s[0] = 0 + println(s) +} + +// Output: +// array[(0 int),(0 int)] diff --git a/gnovm/tests/files/types/assign_literal7c.gno b/gnovm/tests/files/types/assign_literal7c.gno new file mode 100644 index 00000000000..06cd61d64b1 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7c.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + str := "hello" + str[0] = 'y' + fmt.Println(str[0]) + fmt.Printf("%c\n", str[0]) +} + +// Error: +// main/files/types/assign_literal7c.gno:7:2: cannot assign to str[(const (0 int))] diff --git a/gnovm/tests/files/types/assign_literal7d.gno b/gnovm/tests/files/types/assign_literal7d.gno new file mode 100644 index 00000000000..f56c304b3d3 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7d.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func main() { + bs := []byte("hello") + bs[0] = 'y' + fmt.Println(bs[0]) + fmt.Printf("%c\n", bs[0]) +} + +// Output: +// 121 +// y diff --git a/gnovm/tests/files/types/assign_literal8.gno b/gnovm/tests/files/types/assign_literal8.gno new file mode 100644 index 00000000000..d652efc0874 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal8.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var a int + p := &a + *p = 1 + println(*p) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/assign_literal9.gno b/gnovm/tests/files/types/assign_literal9.gno new file mode 100644 index 00000000000..1d672ae46ce --- /dev/null +++ b/gnovm/tests/files/types/assign_literal9.gno @@ -0,0 +1,14 @@ +package main + +type foo struct { + a int +} + +func main() { + s := &foo{} + s.a = 1 + println(s.a) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/assign_literal_a.gno b/gnovm/tests/files/types/assign_literal_a.gno new file mode 100644 index 00000000000..dd17e97c466 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal_a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + 1 := 6 +} + +// Error: +// files/types/assign_literal_a.gno:4:2: no new variables on left side of := \ No newline at end of file diff --git a/gnovm/tests/files/types/assign_nil.gno b/gnovm/tests/files/types/assign_nil.gno new file mode 100644 index 00000000000..8c756da3b60 --- /dev/null +++ b/gnovm/tests/files/types/assign_nil.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var i interface{} + i = 4 + var j int + j, nil = i.(int) +} + +// Error: +// main/files/types/assign_nil.gno:7:2: cannot assign to (const (undefined)) diff --git a/gnovm/tests/files/types/assign_nil2.gno b/gnovm/tests/files/types/assign_nil2.gno new file mode 100644 index 00000000000..fd7d509fccc --- /dev/null +++ b/gnovm/tests/files/types/assign_nil2.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var i interface{} + i = 4 + var ok bool + nil, nil = i.(int) +} + +// Error: +// main/files/types/assign_nil2.gno:7:2: cannot assign to (const (undefined)) diff --git a/gnovm/tests/files/types/assign_range.gno b/gnovm/tests/files/types/assign_range.gno new file mode 100644 index 00000000000..5ee1c49a2e9 --- /dev/null +++ b/gnovm/tests/files/types/assign_range.gno @@ -0,0 +1,42 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := map[string]int{ + "Alice": 92, + "Bob": 89, + "Charlie": 95, + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + for k, v := range scores { + fmt.Printf("%s has a score of %d\n", k, v) + } + + // Modifying values during iteration + for k := range scores { + scores[k] += 5 // Adding 5 to each score + } + fmt.Println("Updated scores:", scores) + + // Collecting keys and values into slices + var keys []string + var values []int + for k, v := range scores { + keys = append(keys, k) + values = append(values, v) + } + fmt.Println("Keys:", keys) + fmt.Println("Values:", values) +} + +// Output: +// Alice has a score of 92 +// Bob has a score of 89 +// Charlie has a score of 95 +// Updated scores: map[Alice:97 Bob:94 Charlie:100] +// Keys: [Alice Bob Charlie] +// Values: [97 94 100] diff --git a/gnovm/tests/files/types/assign_range_a.gno b/gnovm/tests/files/types/assign_range_a.gno new file mode 100644 index 00000000000..807b01da1f8 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_a.gno @@ -0,0 +1,22 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := map[string]int{ + "Alice": 92, + "Bob": 89, + "Charlie": 95, + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v int + for k, v = range scores { + fmt.Printf("%s has a score of %d\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_a.gno:16:2: cannot use string as int diff --git a/gnovm/tests/files/types/assign_range_a1.gno b/gnovm/tests/files/types/assign_range_a1.gno new file mode 100644 index 00000000000..2e882ca189a --- /dev/null +++ b/gnovm/tests/files/types/assign_range_a1.gno @@ -0,0 +1,22 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := map[string]int{ + "Alice": 92, + "Bob": 89, + "Charlie": 95, + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var v int + for 6, v = range scores { + fmt.Printf("%s has a score of %d\n", "a", v) + } +} + +// Error: +// main/files/types/assign_range_a1.gno:16:2: cannot assign to (const (6 bigint)) diff --git a/gnovm/tests/files/types/assign_range_b.gno b/gnovm/tests/files/types/assign_range_b.gno new file mode 100644 index 00000000000..0c822d6f2dd --- /dev/null +++ b/gnovm/tests/files/types/assign_range_b.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := []string{ + "a", "b", "c", + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v int + for k, v = range scores { + fmt.Printf("%d has a score of %s\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_b.gno:14:2: cannot use string as int diff --git a/gnovm/tests/files/types/assign_range_b1.gno b/gnovm/tests/files/types/assign_range_b1.gno new file mode 100644 index 00000000000..e51bd5c1376 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_b1.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := [3]string{ + "a", "b", "c", + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v int + for k, v = range scores { + fmt.Printf("%d has a score of %s\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_b1.gno:14:2: cannot use string as int diff --git a/gnovm/tests/files/types/assign_range_b2.gno b/gnovm/tests/files/types/assign_range_b2.gno new file mode 100644 index 00000000000..a96c3f91ace --- /dev/null +++ b/gnovm/tests/files/types/assign_range_b2.gno @@ -0,0 +1,28 @@ +package main + +func main() { + rangeMap() + rangeSlice() +} + +func rangeMap() { + m := map[int]string{999: "asdf"} + var v int + for v = range m { + println(v) + } +} + +func rangeSlice() { + m := []int{9, 4, 6} + var v int + for v = range m { + println(v) + } +} + +// Output: +// 999 +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/types/assign_range_c.gno b/gnovm/tests/files/types/assign_range_c.gno new file mode 100644 index 00000000000..0611168f9a9 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_c.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := []string{ + "a", "b", "c", + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v float32 + for k, v = range scores { + fmt.Printf("%d has a score of %s\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_c.gno:14:2: index type should be int, but got float32 diff --git a/gnovm/tests/files/types/assign_range_d.gno b/gnovm/tests/files/types/assign_range_d.gno new file mode 100644 index 00000000000..39e7c9a84bc --- /dev/null +++ b/gnovm/tests/files/types/assign_range_d.gno @@ -0,0 +1,14 @@ +package main + +func main() { + s := "hello" + + var index float32 + var value rune + for index, value = range s { + println(index) + } +} + +// Error: +// main/files/types/assign_range_d.gno:8:2: index type should be int, but got float32 diff --git a/gnovm/tests/files/types/assign_range_e.gno b/gnovm/tests/files/types/assign_range_e.gno new file mode 100644 index 00000000000..ada9105c181 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_e.gno @@ -0,0 +1,15 @@ +package main + +func main() { + s := "hello" + + var index int + var value int + for index, value = range s { + println(index) + println(value) + } +} + +// Error: +// main/files/types/assign_range_e.gno:8:2: value type should be int32, but got int diff --git a/gnovm/tests/files/types/assign_rune.gno b/gnovm/tests/files/types/assign_rune.gno new file mode 100644 index 00000000000..7c1e8742fd4 --- /dev/null +++ b/gnovm/tests/files/types/assign_rune.gno @@ -0,0 +1,18 @@ +package main + +var r rune + +// TODO: print right dec, float +func main() { + r = 'a' + println(r) + println(float32(r)) + println(float64(r)) + println(string(r)) +} + +// Output: +// 97 +// 97 +// 97 +// a diff --git a/gnovm/tests/files/types/assign_rune_a.gno b/gnovm/tests/files/types/assign_rune_a.gno new file mode 100644 index 00000000000..caa7e2b31de --- /dev/null +++ b/gnovm/tests/files/types/assign_rune_a.gno @@ -0,0 +1,12 @@ +package main + +var r rune + +// assign +func main() { + r = "a" + println(r) +} + +// Error: +// main/files/types/assign_rune_a.gno:7:2: cannot use untyped string as Int32Kind diff --git a/gnovm/tests/files/types/assign_type_assertion.gno b/gnovm/tests/files/types/assign_type_assertion.gno new file mode 100644 index 00000000000..de989551235 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion.gno @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" +) + +func main() { + var i interface{} = "Hello, world!" + + // Attempt to assert the type of i to string + var n int + var ok bool + n, ok = i.(string) + if ok { + fmt.Println("i contains a string:", n) + } else { + fmt.Println("i does not contain a string") + } +} + +// Error: +// main/files/types/assign_type_assertion.gno:13:2: cannot use string as int diff --git a/gnovm/tests/files/types/assign_type_assertion_a.gno b/gnovm/tests/files/types/assign_type_assertion_a.gno new file mode 100644 index 00000000000..1b28408d309 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_a.gno @@ -0,0 +1,33 @@ +package main + +import "fmt" + +type MyError struct{} + +func (e MyError) Error() string { + return "my error" +} + +func (e MyError) IsSet() bool { + return true +} + +func main() { + var err error = MyError{} + + var assertedErr interface{ IsSet() bool } // Define a variable of the interface type + var ok bool + + // Perform the assertion and assign the result to assertedErr + assertedErr, ok = err.(interface{ IsSet() bool }) + if ok { + fmt.Println("Assertion succeeded:", ok) + fmt.Println("IsSet:", assertedErr.IsSet()) + } else { + fmt.Println("Assertion failed:", ok) + } +} + +// Output: +// Assertion succeeded: true +// IsSet: true diff --git a/gnovm/tests/files/types/assign_type_assertion_b.gno b/gnovm/tests/files/types/assign_type_assertion_b.gno new file mode 100644 index 00000000000..1cd70bde4ec --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_b.gno @@ -0,0 +1,32 @@ +package main + +import "fmt" + +type MyError struct{} + +func (e MyError) Error() string { + return "my error" +} + +func (e MyError) IsSet() bool { + return true +} + +func main() { + var err error = MyError{} + + var assertedErr int // Define a variable of the interface type + var ok bool + + // Perform the assertion and assign the result to assertedErr + assertedErr, ok = err.(interface{ IsSet() bool }) + if ok { + fmt.Println("Assertion succeeded:", ok) + fmt.Println("IsSet:", assertedErr.IsSet()) + } else { + fmt.Println("Assertion failed:", ok) + } +} + +// Error: +// 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 new file mode 100644 index 00000000000..d1c1cadcb91 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_c.gno @@ -0,0 +1,33 @@ +package main + +import "fmt" + +type MyError struct{} + +func (e MyError) Error() string { + return "my error" +} + +func (e MyError) IsSet() bool { + return true +} + +func main() { + var err error = MyError{} + + var assertedErr interface{ IsNotSet() bool } // Define a variable of the interface type + + var ok bool + + // Perform the assertion and assign the result to assertedErr + assertedErr, ok = err.(interface{ IsSet() bool }) // not impl lhs + if ok { + fmt.Println("Assertion succeeded:", ok) + fmt.Println("IsSet:", assertedErr.IsSet()) + } else { + fmt.Println("Assertion failed:", ok) + } +} + +// 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) diff --git a/gnovm/tests/files/types/assign_type_assertion_d.gno b/gnovm/tests/files/types/assign_type_assertion_d.gno new file mode 100644 index 00000000000..7d2f2fdf093 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_d.gno @@ -0,0 +1,25 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var r Robot + + r = animal.(Dog) + + println(r) +} + +// Error: +// main/files/types/assign_type_assertion_d.gno:19:2: cannot use main.Dog as main.Robot without explicit conversion diff --git a/gnovm/tests/files/types/assign_type_assertion_e.gno b/gnovm/tests/files/types/assign_type_assertion_e.gno new file mode 100644 index 00000000000..caa82a48891 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_e.gno @@ -0,0 +1,27 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var r Robot + var ok bool + + r, ok = animal.(Dog) + + println(r) + println(ok) +} + +// Error: +// main/files/types/assign_type_assertion_e.gno:20:2: cannot use main.Dog as main.Robot without explicit conversion diff --git a/gnovm/tests/files/types/assign_type_assertion_f.gno b/gnovm/tests/files/types/assign_type_assertion_f.gno new file mode 100644 index 00000000000..939d391d74e --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_f.gno @@ -0,0 +1,25 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var ok bool + + 1, ok = animal.(Dog) + + println(ok) +} + +// Error: +// main/files/types/assign_type_assertion_f.gno:19:2: cannot assign to (const (1 bigint)) diff --git a/gnovm/tests/files/types/assign_type_assertion_g.gno b/gnovm/tests/files/types/assign_type_assertion_g.gno new file mode 100644 index 00000000000..61035d3c8a8 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_g.gno @@ -0,0 +1,27 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var a Animal + var ok int + + a, ok = animal.(Dog) + + println(a) + println(ok) +} + +// Error: +// main/files/types/assign_type_assertion_g.gno:20:2: want bool type got int diff --git a/gnovm/tests/files/types/bigdec.gno b/gnovm/tests/files/types/bigdec.gno new file mode 100644 index 00000000000..baa3d4d6309 --- /dev/null +++ b/gnovm/tests/files/types/bigdec.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1 + println(a % 1e9) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/bigdec2.gno b/gnovm/tests/files/types/bigdec2.gno new file mode 100644 index 00000000000..4b631698f16 --- /dev/null +++ b/gnovm/tests/files/types/bigdec2.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1 + println(a % 1.2) +} + +// Error: +// main/files/types/bigdec2.gno:7:10: cannot convert untyped bigdec to integer -- 1.2 not an exact integer diff --git a/gnovm/tests/files/types/bigdec3.gno b/gnovm/tests/files/types/bigdec3.gno new file mode 100644 index 00000000000..0e0effa7cbb --- /dev/null +++ b/gnovm/tests/files/types/bigdec3.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1 + println(1.0 % a) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/types/bigdec4.gno b/gnovm/tests/files/types/bigdec4.gno new file mode 100644 index 00000000000..88cbe894020 --- /dev/null +++ b/gnovm/tests/files/types/bigdec4.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1.0 + println(a) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/bigdec5.gno b/gnovm/tests/files/types/bigdec5.gno new file mode 100644 index 00000000000..fa133bd4e39 --- /dev/null +++ b/gnovm/tests/files/types/bigdec5.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1.2 + println(a) +} + +// Error: +// main/files/types/bigdec5.gno:6:2: cannot convert untyped bigdec to integer -- 1.2 not an exact integer diff --git a/gnovm/tests/files/types/bigdec_6.gno b/gnovm/tests/files/types/bigdec_6.gno new file mode 100644 index 00000000000..bf9e0774426 --- /dev/null +++ b/gnovm/tests/files/types/bigdec_6.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(1 % 1e9) +} + +// Error: +// main/files/types/bigdec_6.gno:4:10: operator % not defined on: BigdecKind diff --git a/gnovm/tests/files/types/cmp_array.gno b/gnovm/tests/files/types/cmp_array.gno new file mode 100644 index 00000000000..52fea95b034 --- /dev/null +++ b/gnovm/tests/files/types/cmp_array.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func main() { + a := [2][2]int{{1, 2}, {3, 4}} + b := [2][2]int{{1, 2}, {3, 4}} + c := [2][2]int{{5, 6}, {7, 8}} + + fmt.Println("a == b:", a == b) // True because the elements match exactly + fmt.Println("a == c:", a == c) // False because the elements do not match +} + +// Output: +// a == b: true +// a == c: false diff --git a/gnovm/tests/files/types/cmp_array_a.gno b/gnovm/tests/files/types/cmp_array_a.gno new file mode 100644 index 00000000000..1d30f7c51eb --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_a.gno @@ -0,0 +1,21 @@ +package main + +import "fmt" + +// Define a struct that embeds an array +type Matrix struct { + data [2]int +} + +func main() { + a := [2]Matrix{{data: [2]int{1, 2}}, {data: [2]int{3, 4}}} + b := [2]Matrix{{data: [2]int{1, 2}}, {data: [2]int{3, 4}}} + c := [2]Matrix{{data: [2]int{5, 6}}, {data: [2]int{7, 8}}} + + fmt.Println("a == b:", a == b) // True because the elements match exactly + fmt.Println("a == c:", a == c) // False because the elements do not match +} + +// Output: +// a == b: true +// a == c: false diff --git a/gnovm/tests/files/types/cmp_array_b.gno b/gnovm/tests/files/types/cmp_array_b.gno new file mode 100644 index 00000000000..817f6a605ad --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_b.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func main() { + a := [2][]int{{1, 2}, {3, 4}} + b := [2][]int{{1, 2}, {3, 4}} + c := [2][]int{{5, 6}, {7, 8}} + + fmt.Println("a == b:", a == b) // True because the elements match exactly + fmt.Println("a == c:", a == c) // False because the elements do not match +} + +// Error: +// main/files/types/cmp_array_b.gno:10:25: [2][]int is not comparable diff --git a/gnovm/tests/files/types/cmp_array_c.gno b/gnovm/tests/files/types/cmp_array_c.gno new file mode 100644 index 00000000000..ee5688766dc --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_c.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func main() { + a := [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}} + b := [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}} + c := [2][2][]int{{{5, 6}, {7, 8}}, {{1, 2}, {3, 4}}} + + fmt.Println("a == b:", a == b) + fmt.Println("a == c:", a == c) +} + +// Error: +// main/files/types/cmp_array_c.gno:10:25: [2][]int is not comparable diff --git a/gnovm/tests/files/types/cmp_array_d.gno b/gnovm/tests/files/types/cmp_array_d.gno new file mode 100644 index 00000000000..78f9527f819 --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_d.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + a := [2]int{1, 2} + b := [2]int{1, 2} + + fmt.Println("a == b:", a == b) +} + +// Output: +// a == b: true diff --git a/gnovm/tests/files/types/cmp_pointer.gno b/gnovm/tests/files/types/cmp_pointer.gno new file mode 100644 index 00000000000..52c3ad6efec --- /dev/null +++ b/gnovm/tests/files/types/cmp_pointer.gno @@ -0,0 +1,23 @@ +package main + +import "fmt" + +// Define a Person struct +type Person struct { + Name string + Age int +} + +type Worker string + +func main() { + // Create two pointers to Person instances + p1 := &Person{Name: "Alice", Age: 30} + p2 := Worker("a") + p2Ptr := &p2 + + fmt.Println("p1 == p2:", p1 == p2Ptr) +} + +// Error: +// main/files/types/cmp_pointer.gno:19:27: cannot use main.Person as main.Worker without explicit conversion diff --git a/gnovm/tests/files/types/cmp_pointer2.gno b/gnovm/tests/files/types/cmp_pointer2.gno new file mode 100644 index 00000000000..6899803075f --- /dev/null +++ b/gnovm/tests/files/types/cmp_pointer2.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := []int{1, 2} + b := []int{1, 2} + println(&a == &b) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/cmp_struct.gno b/gnovm/tests/files/types/cmp_struct.gno new file mode 100644 index 00000000000..c3ead1510a4 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct.gno @@ -0,0 +1,23 @@ +package main + +import "fmt" + +// Define a struct that contains an array field +type Matrix struct { + data [2]int +} + +func main() { + // Create two instances of the struct + m1 := Matrix{data: [2]int{1, 2}} + m2 := Matrix{data: [2]int{1, 2}} + m3 := Matrix{data: [2]int{3, 4}} + + // Compare the instances + fmt.Println("m1 == m2:", m1 == m2) // True because the data fields are identical + fmt.Println("m1 == m3:", m1 == m3) // False because the data fields are different +} + +// Output: +// m1 == m2: true +// m1 == m3: false diff --git a/gnovm/tests/files/types/cmp_struct_a.gno b/gnovm/tests/files/types/cmp_struct_a.gno new file mode 100644 index 00000000000..b545ef70178 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_a.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +// Define a struct that wraps the three-dimensional data structure +type Matrix struct { + data [2][2][]int +} + +func main() { + a := Matrix{data: [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}} + b := Matrix{data: [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}} + c := Matrix{data: [2][2][]int{{{5, 6}, {7, 8}}, {{1, 2}, {3, 4}}}} + + fmt.Println("a == b:", a == b) + fmt.Println("a == c:", a == c) +} + +// Error: +// main/files/types/cmp_struct_a.gno:15:25: [2][]int is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_b.gno b/gnovm/tests/files/types/cmp_struct_b.gno new file mode 100644 index 00000000000..eb3de3e3cc1 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_b.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a int +} + +type bar struct { + b int +} + +func main() { + fa := foo{} + bb := bar{} + + println(fa == bb) +} + +// Error: +// main/files/types/cmp_struct_b.gno:15:10: cannot use main.foo as main.bar without explicit conversion diff --git a/gnovm/tests/files/types/cmp_struct_c.gno b/gnovm/tests/files/types/cmp_struct_c.gno new file mode 100644 index 00000000000..3ed31109260 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_c.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a int +} + +type bar struct { + b []int +} + +func main() { + fa := foo{} + bb := bar{} + + println(fa == bb) +} + +// Error: +// main/files/types/cmp_struct_c.gno:15:10: main.bar is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_c1.gno b/gnovm/tests/files/types/cmp_struct_c1.gno new file mode 100644 index 00000000000..5220abe4662 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_c1.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a int +} + +type bar struct { + b []int +} + +func main() { + fa := foo{} + bb := bar{} + + println(bb == fa) +} + +// Error: +// main/files/types/cmp_struct_c1.gno:15:10: cannot use main.bar as main.foo without explicit conversion diff --git a/gnovm/tests/files/types/cmp_struct_d.gno b/gnovm/tests/files/types/cmp_struct_d.gno new file mode 100644 index 00000000000..19e0c4229b1 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_d.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a []int +} + +type bar struct { + b []int +} + +func main() { + fa := foo{} + bb := bar{} + + println(fa == bb) +} + +// Error: +// main/files/types/cmp_struct_d.gno:15:10: main.bar is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_e.gno b/gnovm/tests/files/types/cmp_struct_e.gno new file mode 100644 index 00000000000..46e4b332a58 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_e.gno @@ -0,0 +1,15 @@ +package main + +type foo struct { + a []int +} + +func main() { + fa := foo{} + fb := foo{} + + println(fa == fb) +} + +// Error: +// main/files/types/cmp_struct_e.gno:11:10: main.foo is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_f.gno b/gnovm/tests/files/types/cmp_struct_f.gno new file mode 100644 index 00000000000..20366b625e9 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_f.gno @@ -0,0 +1,43 @@ +package main + +import "fmt" + +// Define a Person struct +type Person struct { + Name string + Age int +} + +// Define an Employee struct that embeds Person +type Employee struct { + Person + Department string + ID int +} + +func main() { + emp1 := Employee{ + Person: Person{Name: "John Doe", Age: 30}, + Department: "Engineering", + ID: 12345, + } + + emp2 := Employee{ + Person: Person{Name: "John Doe", Age: 30}, + Department: "Engineering", + ID: 12345, + } + + emp3 := Employee{ + Person: Person{Name: "Jane Doe", Age: 29}, + Department: "Marketing", + ID: 67890, + } + + fmt.Println("emp1 == emp2:", emp1 == emp2) // True because all fields match + fmt.Println("emp1 == emp3:", emp1 == emp3) // False because some fields differ +} + +// Output: +// emp1 == emp2: true +// emp1 == emp3: false diff --git a/gnovm/tests/files/types/cmp_struct_g.gno b/gnovm/tests/files/types/cmp_struct_g.gno new file mode 100644 index 00000000000..4d296d7b0a7 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_g.gno @@ -0,0 +1,21 @@ +package main + +import "fmt" + +type Person struct { + age int +} + +type Dog struct { + age int +} + +func main() { + a := Person{} + b := Dog{} + + fmt.Println("a == b:", a == b) +} + +// Error: +// main/files/types/cmp_struct_g.gno:17:25: cannot use main.Person as main.Dog without explicit conversion diff --git a/gnovm/tests/files/types/eql_0a0.gno b/gnovm/tests/files/types/eql_0a0.gno new file mode 100644 index 00000000000..0a9695aa3e6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) == int8(1)) +} + +// Error: +// main/files/types/eql_0a0.gno:5:10: cannot use int as int8 diff --git a/gnovm/tests/files/types/eql_0a01.gno b/gnovm/tests/files/types/eql_0a01.gno new file mode 100644 index 00000000000..b394e13aecf --- /dev/null +++ b/gnovm/tests/files/types/eql_0a01.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(nil == nil) // xt: , dt: +} + +// Error: +// main/files/types/eql_0a01.gno:4:10: is not comparable diff --git a/gnovm/tests/files/types/eql_0a02.gno b/gnovm/tests/files/types/eql_0a02.gno new file mode 100644 index 00000000000..86668812731 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a02.gno @@ -0,0 +1,11 @@ +package main + +func main() { + intPtr := new(int) + *intPtr = 5 + s := "hello" + println(intPtr == s) +} + +// Error: +// main/files/types/eql_0a02.gno:7:10: cannot use *int as string diff --git a/gnovm/tests/files/types/eql_0a03.gno b/gnovm/tests/files/types/eql_0a03.gno new file mode 100644 index 00000000000..d9a32304a3b --- /dev/null +++ b/gnovm/tests/files/types/eql_0a03.gno @@ -0,0 +1,12 @@ +package main + +func main() { + intPtr := new(int8) + *intPtr = 5 + + i := 0 + println(intPtr == &i) +} + +// Error: +// main/files/types/eql_0a03.gno:8:10: cannot use int8 as int diff --git a/gnovm/tests/files/types/eql_0a1.gno b/gnovm/tests/files/types/eql_0a1.gno new file mode 100644 index 00000000000..fef2654b1ec --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) != int8(1)) +} + +// Error: +// main/files/types/eql_0a1.gno:5:10: cannot use int as int8 diff --git a/gnovm/tests/files/types/eql_0a1a.gno b/gnovm/tests/files/types/eql_0a1a.gno new file mode 100644 index 00000000000..deb369511fd --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1a.gno @@ -0,0 +1,11 @@ +package main + +// left typed, right untyped +func main() { + println(int(1) == 1) + println(int(1) != 1) +} + +// Output: +// true +// false diff --git a/gnovm/tests/files/types/eql_0a1a0.gno b/gnovm/tests/files/types/eql_0a1a0.gno new file mode 100644 index 00000000000..eaa099fc067 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1a0.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := uint(1) + println(uint64(1) == a) +} + +// Error: +// main/files/types/eql_0a1a0.gno:5:10: cannot use uint64 as uint diff --git a/gnovm/tests/files/types/eql_0a1a1.gno b/gnovm/tests/files/types/eql_0a1a1.gno new file mode 100644 index 00000000000..34c59fe1dcc --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1a1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := uint(1) + println(a == uint64(1)) +} + +// Error: +// main/files/types/eql_0a1a1.gno:5:10: cannot use uint as uint64 diff --git a/gnovm/tests/files/types/eql_0a1b.gno b/gnovm/tests/files/types/eql_0a1b.gno new file mode 100644 index 00000000000..1db537a4d8c --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1b.gno @@ -0,0 +1,20 @@ +package main + +type S struct { + expected string +} + +// special case when RHS is result of slice operation, its type is determined in runtime +func main() { + s := S{ + expected: `hello`[:], // this is not converted + } + + a := "hello" + + println(a == s.expected) + +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1c.gno b/gnovm/tests/files/types/eql_0a1c.gno new file mode 100644 index 00000000000..76f2db8d7d8 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1c.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := "hello" + println(a == expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1d.gno b/gnovm/tests/files/types/eql_0a1d.gno new file mode 100644 index 00000000000..018d53fa81a --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1d.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + expected string +} + +func main() { + println("hello" == S{ + expected: `hello`[:], + }.expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1e.gno b/gnovm/tests/files/types/eql_0a1e.gno new file mode 100644 index 00000000000..2795d618e91 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1e.gno @@ -0,0 +1,16 @@ +package main + +type S struct { + expected string +} + +func main() { + var s = S{ + expected: `hello`[:], + } + a := "hello" + println(a == s.expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1f.gno b/gnovm/tests/files/types/eql_0a1f.gno new file mode 100644 index 00000000000..27ac5ffc761 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1f.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := 1 + println(a == expected) // both typed +} + +// Error: +// main/files/types/eql_0a1f.gno:6:10: cannot use int as string diff --git a/gnovm/tests/files/types/eql_0a1g.gno b/gnovm/tests/files/types/eql_0a1g.gno new file mode 100644 index 00000000000..a77c1146cac --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1g.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a int = 1 + var b float32 = 1.0 + println(a == b) // both typed +} + +// Error: +// main/files/types/eql_0a1g.gno:6:10: cannot use int as float32 diff --git a/gnovm/tests/files/types/eql_0a2.gno b/gnovm/tests/files/types/eql_0a2.gno new file mode 100644 index 00000000000..41eaf4d28de --- /dev/null +++ b/gnovm/tests/files/types/eql_0a2.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) == Error2(0)) +} + +// Error: +// main/files/types/eql_0a2.gno:21:10: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0a3.gno b/gnovm/tests/files/types/eql_0a3.gno new file mode 100644 index 00000000000..5aff635337d --- /dev/null +++ b/gnovm/tests/files/types/eql_0a3.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) != Error2(0)) +} + +// Error: +// main/files/types/eql_0a3.gno:21:10: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0a4.gno b/gnovm/tests/files/types/eql_0a4.gno new file mode 100644 index 00000000000..8ed3f753368 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a4.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) != Error2(0)) +} + +// Error: +// main/files/types/eql_0a4.gno:21:10: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0b0.gno b/gnovm/tests/files/types/eql_0b0.gno new file mode 100644 index 00000000000..2c968b5158f --- /dev/null +++ b/gnovm/tests/files/types/eql_0b0.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is untyped const, right is typed const +// left is assignable to right +func main() { + if 1 == Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0b1.gno b/gnovm/tests/files/types/eql_0b1.gno new file mode 100644 index 00000000000..a7bdf5275da --- /dev/null +++ b/gnovm/tests/files/types/eql_0b1.gno @@ -0,0 +1,23 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is untyped const, right is typed const +func main() { + if 1 != Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/eql_0b2.gno b/gnovm/tests/files/types/eql_0b2.gno new file mode 100644 index 00000000000..4c7dee53fe6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b2.gno @@ -0,0 +1,22 @@ +package main + +type Error string + +func (e Error) Error() string { + return "error: " + string(e) +} + +// left is untyped const, right is typed const +// left is not assignable to right +// a) it's (untyped) bigint +// b) base type of right is string +func main() { + if 1 == Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0b2.gno:14:5: cannot use untyped Bigint as StringKind diff --git a/gnovm/tests/files/types/eql_0b3.gno b/gnovm/tests/files/types/eql_0b3.gno new file mode 100644 index 00000000000..34c8a24cba2 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b3.gno @@ -0,0 +1,15 @@ +package main + +var a int8 + +func main() { + a = 1 + if 1 == a { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0b4_native.gno b/gnovm/tests/files/types/eql_0b4_native.gno new file mode 100644 index 00000000000..7c7baf01924 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b4_native.gno @@ -0,0 +1,13 @@ +package main + +import ( + "errors" +) + +func main() { + errCmp := errors.New("xxx") + println(5 == errCmp) +} + +// Error: +// main/files/types/eql_0b4_native.gno:9:10: unexpected type pair: cannot use bigint as gonative{error} diff --git a/gnovm/tests/files/types/eql_0b4_stdlibs.gno b/gnovm/tests/files/types/eql_0b4_stdlibs.gno new file mode 100644 index 00000000000..eac923c6d31 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b4_stdlibs.gno @@ -0,0 +1,13 @@ +package main + +import ( + "errors" +) + +func main() { + errCmp := errors.New("xxx") + println(5 == errCmp) +} + +// Error: +// main/files/types/eql_0b4_stdlibs.gno:9:10: bigint does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0c2.gno b/gnovm/tests/files/types/eql_0c2.gno new file mode 100644 index 00000000000..f36ec92311c --- /dev/null +++ b/gnovm/tests/files/types/eql_0c2.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is typed const, right is untyped const +// NOTE: overflow +func main() { + if Error(1) == 128 { // note, this would overflow as expected + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0c2.gno:16:5: bigint overflows target kind diff --git a/gnovm/tests/files/types/eql_0d0.gno b/gnovm/tests/files/types/eql_0d0.gno new file mode 100644 index 00000000000..6890d450bab --- /dev/null +++ b/gnovm/tests/files/types/eql_0d0.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +func main() { + println(1.0 == 1) + println(1.0 == 0) + println(float32(1.0) == 0) +} + +// Output: +// true +// false +// false diff --git a/gnovm/tests/files/types/eql_0e0.gno b/gnovm/tests/files/types/eql_0e0.gno new file mode 100644 index 00000000000..d12c43066f8 --- /dev/null +++ b/gnovm/tests/files/types/eql_0e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 == e2) +} + +// Error: +// main/files/types/eql_0e0.gno:23:10: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0e1.gno b/gnovm/tests/files/types/eql_0e1.gno new file mode 100644 index 00000000000..fe8f6d21a39 --- /dev/null +++ b/gnovm/tests/files/types/eql_0e1.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 != e2) +} + +// Error: +// main/files/types/eql_0e1.gno:23:10: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0f0_native.gno b/gnovm/tests/files/types/eql_0f0_native.gno new file mode 100644 index 00000000000..e32325f5cf6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f0_native.gno @@ -0,0 +1,28 @@ +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 1 == errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f0_native.gno:19:5: unexpected type pair: cannot use bigint as gonative{error} diff --git a/gnovm/tests/files/types/eql_0f0_stdlibs.gno b/gnovm/tests/files/types/eql_0f0_stdlibs.gno new file mode 100644 index 00000000000..4947627cba4 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f0_stdlibs.gno @@ -0,0 +1,28 @@ +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 1 == errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f0_stdlibs.gno:19:5: bigint does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f12.gno b/gnovm/tests/files/types/eql_0f12.gno new file mode 100644 index 00000000000..751852d0b70 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f12.gno @@ -0,0 +1,13 @@ +package main + +var a [2]string +var b [2]string + +func main() { + a = [2]string{"hello", "world"} + b = [2]string{"hello", "world"} + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f14.gno b/gnovm/tests/files/types/eql_0f14.gno new file mode 100644 index 00000000000..babae912de9 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f14.gno @@ -0,0 +1,14 @@ +package main + +var a [2]string +var c [2]int + +// TODO: should stop at comparable check +func main() { + a = [2]string{"hello", "world"} + c = [2]int{1, 2} + println(a == c) +} + +// Error: +// main/files/types/eql_0f14.gno:10:10: cannot use [2]string as [2]int diff --git a/gnovm/tests/files/types/eql_0f15.gno b/gnovm/tests/files/types/eql_0f15.gno new file mode 100644 index 00000000000..36077127332 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f15.gno @@ -0,0 +1,17 @@ +package main + +var a [2]interface{} +var c [2]interface{} + +func gen() interface{} { + return 1 +} + +func main() { + a = [2]interface{}{gen(), gen()} + c = [2]interface{}{gen(), gen()} + println(a == c) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f16.gno b/gnovm/tests/files/types/eql_0f16.gno new file mode 100644 index 00000000000..d5654fd6c45 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f16.gno @@ -0,0 +1,20 @@ +package main + +type word []int + +var a [2]word +var c [2]word + +func gen() word { + return []int{1} +} + +// TODO: consider log desc +func main() { + a = [2]word{gen(), gen()} + c = [2]word{gen(), gen()} + println(a == c) +} + +// Error: +// main/files/types/eql_0f16.gno:16:10: [2]main.word is not comparable diff --git a/gnovm/tests/files/types/eql_0f17.gno b/gnovm/tests/files/types/eql_0f17.gno new file mode 100644 index 00000000000..ecb9800ce4d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f17.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +var a f = func() bool { return true } +var b f = func() bool { return false } + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f17.gno:9:10: main.f can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f18.gno b/gnovm/tests/files/types/eql_0f18.gno new file mode 100644 index 00000000000..ad534066fd1 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f18.gno @@ -0,0 +1,12 @@ +package main + +type f func() bool + +var a f = func() bool { return true } + +func main() { + println(a == nil) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f19.gno b/gnovm/tests/files/types/eql_0f19.gno new file mode 100644 index 00000000000..8b08d616a06 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f19.gno @@ -0,0 +1,12 @@ +package main + +type f func() bool + +var a f + +func main() { + println(a == nil) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f1_stdlibs.gno b/gnovm/tests/files/types/eql_0f1_stdlibs.gno new file mode 100644 index 00000000000..cab7fcfab33 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f1_stdlibs.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + if int64(1) == errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f1_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f20.gno b/gnovm/tests/files/types/eql_0f20.gno new file mode 100644 index 00000000000..9d5fe9e2681 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f20.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +// slice would be comparable +var a [2]f + +func main() { + println(a == nil) // rcx.T == nil +} + +// Error: +// main/files/types/eql_0f20.gno:9:10: [2]main.f is not comparable diff --git a/gnovm/tests/files/types/eql_0f21.gno b/gnovm/tests/files/types/eql_0f21.gno new file mode 100644 index 00000000000..09d8abb48c3 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f21.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +var a [2]f +var b [2]f + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f21.gno:9:10: [2]main.f is not comparable diff --git a/gnovm/tests/files/types/eql_0f21a.gno b/gnovm/tests/files/types/eql_0f21a.gno new file mode 100644 index 00000000000..075149d264f --- /dev/null +++ b/gnovm/tests/files/types/eql_0f21a.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +var a [2]*f +var b [2]*f + +func main() { + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f22.gno b/gnovm/tests/files/types/eql_0f22.gno new file mode 100644 index 00000000000..75891def307 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f22.gno @@ -0,0 +1,11 @@ +package main + +var a int = 0 +var b int = 1 + +func main() { + println(&a == &b) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f23.gno b/gnovm/tests/files/types/eql_0f23.gno new file mode 100644 index 00000000000..55f2ab5189d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f23.gno @@ -0,0 +1,13 @@ +package main + +var a int = 0 +var b int = 1 + +func main() { + c := &a + d := &b + println(*c == *d) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f24.gno b/gnovm/tests/files/types/eql_0f24.gno new file mode 100644 index 00000000000..d01a9d4a14a --- /dev/null +++ b/gnovm/tests/files/types/eql_0f24.gno @@ -0,0 +1,13 @@ +package main + +var a int = 0 +var b int = 0 + +func main() { + c := &a + d := &b + println(*c == *d) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f25.gno b/gnovm/tests/files/types/eql_0f25.gno new file mode 100644 index 00000000000..8a3343146b0 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f25.gno @@ -0,0 +1,19 @@ +package main + +var a int = 0 +var b int = 0 + +func main() { + c := &a + d := &b + println(*c > *d) + println(*c >= *d) + println(*c < *d) + println(*c <= *d) +} + +// Output: +// false +// true +// false +// true diff --git a/gnovm/tests/files/types/eql_0f27_stdlibs.gno b/gnovm/tests/files/types/eql_0f27_stdlibs.gno new file mode 100644 index 00000000000..188153aeb51 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f27_stdlibs.gno @@ -0,0 +1,21 @@ +package main + +import ( + "errors" +) + +var errCmp1 = errors.New("XXXX") +var errCmp2 = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + if errCmp1 > errCmp2 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f27_stdlibs.gno:13:5: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f28.gno b/gnovm/tests/files/types/eql_0f28.gno new file mode 100644 index 00000000000..c27bcce9286 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f28.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 > e2) +} + +// Error: +// main/files/types/eql_0f28.gno:27:10: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f29.gno b/gnovm/tests/files/types/eql_0f29.gno new file mode 100644 index 00000000000..e754c026c81 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f29.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var l interface{} + if l > Error(0) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f29.gno:16:5: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2b_native.gno b/gnovm/tests/files/types/eql_0f2b_native.gno new file mode 100644 index 00000000000..9de6155c5be --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2b_native.gno @@ -0,0 +1,28 @@ +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 { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2b_native.gno:19:5: operator <= not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2b_stdlibs.gno b/gnovm/tests/files/types/eql_0f2b_stdlibs.gno new file mode 100644 index 00000000000..ac3616d163d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2b_stdlibs.gno @@ -0,0 +1,28 @@ +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 { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2b_stdlibs.gno:19:5: operator <= not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2c_native.gno b/gnovm/tests/files/types/eql_0f2c_native.gno new file mode 100644 index 00000000000..edd5ac3f23a --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2c_native.gno @@ -0,0 +1,28 @@ +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 { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2c_native.gno:19:5: operator < not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2c_stdlibs.gno b/gnovm/tests/files/types/eql_0f2c_stdlibs.gno new file mode 100644 index 00000000000..3a6ac3395b6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2c_stdlibs.gno @@ -0,0 +1,28 @@ +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 { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2c_stdlibs.gno:19:5: operator < not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2d.gno b/gnovm/tests/files/types/eql_0f2d.gno new file mode 100644 index 00000000000..5ad121f515b --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2d.gno @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "strconv" +) + +type E interface { + Error() string +} +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// special case: +// one is interface +func main() { + var e0 E + e0 = Error(0) + fmt.Printf("%T \n", e0) + if e0 == Error(0) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// int64 +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0f2e.gno b/gnovm/tests/files/types/eql_0f2e.gno new file mode 100644 index 00000000000..ea03028f5e1 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2e.gno @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "strconv" +) + +type E interface { + Error() string +} +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// special case: +// one is interface +func main() { + var e0 E + e0 = Error(0) + fmt.Printf("%T \n", e0) + if Error(0) == e0 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// int64 +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0f30.gno b/gnovm/tests/files/types/eql_0f30.gno new file mode 100644 index 00000000000..562122db2a7 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30.gno @@ -0,0 +1,9 @@ +package main + +// both not const, and both interface +func main() { + println([]byte("a") == []byte("b")) +} + +// Error: +// main/files/types/eql_0f30.gno:5:10: []uint8 can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30a.gno b/gnovm/tests/files/types/eql_0f30a.gno new file mode 100644 index 00000000000..7605ca38e0f --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(map[string]int{"a": 1} == map[string]int{"b": 2}) +} + +// Error: +// main/files/types/eql_0f30a.gno:4:10: map[string]int can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30b.gno b/gnovm/tests/files/types/eql_0f30b.gno new file mode 100644 index 00000000000..a9c8247d451 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30b.gno @@ -0,0 +1,13 @@ +package main + +type f func() + +var f1 f +var f2 f + +func main() { + println(f1 == f2) +} + +// Error: +// main/files/types/eql_0f30b.gno:9:10: main.f can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30c.gno b/gnovm/tests/files/types/eql_0f30c.gno new file mode 100644 index 00000000000..ffa8c7c96e7 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30c.gno @@ -0,0 +1,10 @@ +package main + +var f1 func() + +func main() { + println(f1 == nil) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f30d.gno b/gnovm/tests/files/types/eql_0f30d.gno new file mode 100644 index 00000000000..4bbf7e0087f --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30d.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println([]int{1} == []int{1}) +} + +// Error: +// main/files/types/eql_0f30d.gno:4:10: []int can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30e.gno b/gnovm/tests/files/types/eql_0f30e.gno new file mode 100644 index 00000000000..002910072c3 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30e.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println([]int{1} == nil) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f30f.gno b/gnovm/tests/files/types/eql_0f30f.gno new file mode 100644 index 00000000000..9e2eca70d17 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30f.gno @@ -0,0 +1,11 @@ +package main + +var a = [2]int{1, 1} +var b = [3]int{1, 1} + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f30f.gno:7:10: cannot use [2]int as [3]int diff --git a/gnovm/tests/files/types/eql_0f30g.gno b/gnovm/tests/files/types/eql_0f30g.gno new file mode 100644 index 00000000000..558e09921f5 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30g.gno @@ -0,0 +1,11 @@ +package main + +var a = [2]int{1, 1} +var b = []int{1, 1} + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f30g.gno:7:10: []int can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f31.gno b/gnovm/tests/files/types/eql_0f31.gno new file mode 100644 index 00000000000..bcb62dc76c3 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f31.gno @@ -0,0 +1,13 @@ +package main + +import "bytes" + +// both not const, and both interface +func main() { + // lv.T: * uint8, rv.T: * uint8 + cmp := bytes.Compare([]byte("a"), []byte("b")) + println(cmp) +} + +// Output: +// -1 diff --git a/gnovm/tests/files/types/eql_0f32.gno b/gnovm/tests/files/types/eql_0f32.gno new file mode 100644 index 00000000000..321824566cb --- /dev/null +++ b/gnovm/tests/files/types/eql_0f32.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println([]byte("a") == nil) // lx: (const (slice[0x61] []uint8)), rx: (const (undefined)), cmp = 0 +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f33.gno b/gnovm/tests/files/types/eql_0f33.gno new file mode 100644 index 00000000000..e7aac0cf069 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f33.gno @@ -0,0 +1,49 @@ +package main + +import "fmt" + +// var f func() +var a *struct{} +var b interface{} +var c map[string]int +var s []int + +func main() { + if a == nil { + fmt.Println("pointer == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if b == nil { + fmt.Println("interface == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if c == nil { + fmt.Println("map == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if s == nil { + fmt.Println("slice == nil") + } else { + println("not nil!") + } + fmt.Println("----") +} + +// Output: +// pointer == nil +// ---- +// interface == nil +// ---- +// map == nil +// ---- +// slice == nil +// ---- diff --git a/gnovm/tests/files/types/eql_0f34.gno b/gnovm/tests/files/types/eql_0f34.gno new file mode 100644 index 00000000000..8b5d3830091 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f34.gno @@ -0,0 +1,49 @@ +package main + +import "fmt" + +// var f func() +var a *struct{} +var b interface{} +var c map[string]int +var s []int + +func main() { + if nil == a { + fmt.Println("pointer == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if nil == b { + fmt.Println("interface == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if nil == c { + fmt.Println("map == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if nil == s { + fmt.Println("slice == nil") + } else { + println("not nil!") + } + fmt.Println("----") +} + +// Output: +// pointer == nil +// ---- +// interface == nil +// ---- +// map == nil +// ---- +// slice == nil +// ---- diff --git a/gnovm/tests/files/types/eql_0f35.gno b/gnovm/tests/files/types/eql_0f35.gno new file mode 100644 index 00000000000..c57d3e9d375 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f35.gno @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Error0 int64 + +func (e Error0) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func main() { + defer func() { + if r := recover(); r != nil { + if r == Error1(0) { + fmt.Println("Recovered. Error:\n", r) + } + } + }() + + panic(Error1(0)) +} + +// Output: +// Recovered. Error: +// 0 diff --git a/gnovm/tests/files/types/eql_0f37.gno b/gnovm/tests/files/types/eql_0f37.gno new file mode 100644 index 00000000000..7fb051961ee --- /dev/null +++ b/gnovm/tests/files/types/eql_0f37.gno @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Error0 int64 + +func (e Error0) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func get() interface{} { + return Error1(0) +} + +func main() { + defer func() { + if r := recover(); r != nil { + if r == get() { + fmt.Println("recover Error1") + } else { + fmt.Println("recover Error0") + } + } + }() + + panic(Error1(0)) +} + +// Output: +// recover Error1 diff --git a/gnovm/tests/files/types/eql_0f40_stdlibs.gno b/gnovm/tests/files/types/eql_0f40_stdlibs.gno new file mode 100644 index 00000000000..d826bdbd155 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f40_stdlibs.gno @@ -0,0 +1,41 @@ +package main + +import ( + "errors" +) + +type animal interface { + eat() +} + +type dog struct { +} + +func (d *dog) eat() { + println("dog eating") +} + +func get() animal { + d := &dog{} + return d +} + +var errCmp = errors.New("errCmp") + +// no empty interface, different interface(with different methods) +func main() { + defer func() { + if r := recover(); r != nil { + if r == errCmp { + println("same error") + } else { + println("different error") + } + } + }() + + panic(get()) +} + +// Output: +// different error diff --git a/gnovm/tests/files/types/eql_0f41_stdlibs.gno b/gnovm/tests/files/types/eql_0f41_stdlibs.gno new file mode 100644 index 00000000000..be78ea6ed79 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f41_stdlibs.gno @@ -0,0 +1,35 @@ +package main + +import ( + "errors" +) + +type animal interface { + eat() +} + +type dog struct { +} + +func (d *dog) eat() { + println("dog eating") +} + +func get() animal { + d := &dog{} + return d +} + +var errCmp = errors.New("errCmp") + +// no empty interface, different interface(with different methods) +func main() { + if get() == errCmp { + println("same error") + } else { + println("different error") + } +} + +// Error: +// main/files/types/eql_0f41_stdlibs.gno:27:5: main.animal does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f42.gno b/gnovm/tests/files/types/eql_0f42.gno new file mode 100644 index 00000000000..efb63d40b97 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f42.gno @@ -0,0 +1,10 @@ +package main + +var m map[string]int + +func main() { + println(m == nil) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f43_hasNil.gno b/gnovm/tests/files/types/eql_0f43_hasNil.gno new file mode 100644 index 00000000000..c336aa92ef6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f43_hasNil.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func main() { + var myPointer *int + if myPointer == nil { // rcx.T == nil, <-, after conversion lv: (nil *int), rv: (nil *int) + fmt.Println("Pointer is nil") + } else { + fmt.Println("Pointer is not nil") + } +} + +// Output: +// Pointer is nil diff --git a/gnovm/tests/files/types/eql_0f44.gno b/gnovm/tests/files/types/eql_0f44.gno new file mode 100644 index 00000000000..e23e6c07aba --- /dev/null +++ b/gnovm/tests/files/types/eql_0f44.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +type f func() + +func main() { + if f(nil) == nil { // rcx.T == nil + fmt.Println("func is nil") + } else { + fmt.Println("func is not nil") + } +} + +// Output: +// func is nil diff --git a/gnovm/tests/files/types/eql_0f45.gno b/gnovm/tests/files/types/eql_0f45.gno new file mode 100644 index 00000000000..7707136b82e --- /dev/null +++ b/gnovm/tests/files/types/eql_0f45.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func main() { + if func() {} == nil { + fmt.Println("func is nil") + } else { + fmt.Println("func is not nil") + } +} + +// Output: +// func is not nil diff --git a/gnovm/tests/files/types/eql_0f46.gno b/gnovm/tests/files/types/eql_0f46.gno new file mode 100644 index 00000000000..3a65bd18b3b --- /dev/null +++ b/gnovm/tests/files/types/eql_0f46.gno @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type m map[string]int + +func main() { + // m(nil) is a conversion from nil to m, whose underlying type is map[string]int + // lv: (nil main.m), rv: (undefined) + if m(nil) == nil { + fmt.Println("m is nil") + } else { + fmt.Println("m is not nil") + } +} + +// Output: +// m is nil diff --git a/gnovm/tests/files/types/eql_0f46a.gno b/gnovm/tests/files/types/eql_0f46a.gno new file mode 100644 index 00000000000..9476b1f22ef --- /dev/null +++ b/gnovm/tests/files/types/eql_0f46a.gno @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type s []int + +func main() { + // s(nil) is a conversion from nil to s, whose underlying type is []int + // lv: (nil main.s), rv: (undefined) + if s(nil) == nil { + fmt.Println("s is nil") + } else { + fmt.Println("s is not nil") + } +} + +// Output: +// s is nil diff --git a/gnovm/tests/files/types/eql_0f48.gno b/gnovm/tests/files/types/eql_0f48.gno new file mode 100644 index 00000000000..fbf4f164c9d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f48.gno @@ -0,0 +1,22 @@ +package main + +func testEql(want, got interface{}) { + if want != got { + println(false) + } else { + println(true) + } +} + +// return var of error nil +func gen() error { + return nil +} + +func main() { // about untyped nil to (interface{})typed-nil, no support for native for now. + r := gen() + testEql(r, error(nil)) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f8_stdlibs.gno b/gnovm/tests/files/types/eql_0f8_stdlibs.gno new file mode 100644 index 00000000000..a6e24110432 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f8_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/eql_0f8_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_iface.gno b/gnovm/tests/files/types/eql_iface.gno new file mode 100644 index 00000000000..d3d41348a62 --- /dev/null +++ b/gnovm/tests/files/types/eql_iface.gno @@ -0,0 +1,13 @@ +package main + +type Foo struct { + n int +} + +func main() { + var l interface{} = 1 + println(1 == l) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/incdec_a0.gno b/gnovm/tests/files/types/incdec_a0.gno new file mode 100644 index 00000000000..8f3f97caaee --- /dev/null +++ b/gnovm/tests/files/types/incdec_a0.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a++ + println(a) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/incdec_a1.gno b/gnovm/tests/files/types/incdec_a1.gno new file mode 100644 index 00000000000..027ea1c9843 --- /dev/null +++ b/gnovm/tests/files/types/incdec_a1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := "hello" + a++ + println(a) +} + +// Error: +// main/files/types/incdec_a1.gno:5:2: operator ++ not defined on: StringKind diff --git a/gnovm/tests/files/types/incdec_a2.gno b/gnovm/tests/files/types/incdec_a2.gno new file mode 100644 index 00000000000..a91080a0d95 --- /dev/null +++ b/gnovm/tests/files/types/incdec_a2.gno @@ -0,0 +1,29 @@ +package main + +func main() { + a := int(1) + a++ + println(a) + + // TODO: no support for now + //b := 1.0 + //b++ + //println(b) + // + //c := float32(1.0) + //c++ + //println(c) + + d := 'a' + d++ + println(d) + + e := uint(1) + e++ + println(e) +} + +// Output: +// 2 +// 98 +// 2 diff --git a/gnovm/tests/files/types/incdec_a3.gno b/gnovm/tests/files/types/incdec_a3.gno new file mode 100644 index 00000000000..f257b822f8e --- /dev/null +++ b/gnovm/tests/files/types/incdec_a3.gno @@ -0,0 +1,19 @@ +package main + +type Int int8 + +func (i Int) Inc() Int { + i++ + return i +} + +// right is typed const, can not use as its correspondence declared type +func main() { + var a Int + a = Int(int8(0)) + a = a.Inc() + println(a) +} + +// Output: +// (1 main.Int) diff --git a/gnovm/tests/files/types/incdec_a4.gno b/gnovm/tests/files/types/incdec_a4.gno new file mode 100644 index 00000000000..2c33df97c67 --- /dev/null +++ b/gnovm/tests/files/types/incdec_a4.gno @@ -0,0 +1,18 @@ +package main + +type Int int8 + +func (i Int) Inc() { + i++ +} + +// right is typed const, can not use as its correspondence declared type +func main() { + var a Int + a = int8(0) + a.Inc() + println(a) +} + +// Error: +// main/files/types/incdec_a4.gno:12:2: cannot use int8 as main.Int without explicit conversion diff --git a/gnovm/tests/files/types/nil.gno b/gnovm/tests/files/types/nil.gno new file mode 100644 index 00000000000..a67ad119cac --- /dev/null +++ b/gnovm/tests/files/types/nil.gno @@ -0,0 +1,9 @@ +package main + +func main() { + + println(nil + nil) +} + +// Error: +// main/files/types/nil.gno:5:10: operator + not defined on: nil diff --git a/gnovm/tests/files/types/or_a0.gno b/gnovm/tests/files/types/or_a0.gno new file mode 100644 index 00000000000..9896d733c00 --- /dev/null +++ b/gnovm/tests/files/types/or_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(0) | int8(1)) +} + +// Error: +// main/files/types/or_a0.gno:5:10: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/or_a1.gno b/gnovm/tests/files/types/or_a1.gno new file mode 100644 index 00000000000..259eacb4387 --- /dev/null +++ b/gnovm/tests/files/types/or_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) | Error2(0)) +} + +// Error: +// main/files/types/or_a1.gno:21:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/or_b0.gno b/gnovm/tests/files/types/or_b0.gno new file mode 100644 index 00000000000..655121ee2f2 --- /dev/null +++ b/gnovm/tests/files/types/or_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 | Error(1)) + println(Error(1) | 1) +} + +// Output: +// (1 main.Error) +// (1 main.Error) diff --git a/gnovm/tests/files/types/or_b1.gno b/gnovm/tests/files/types/or_b1.gno new file mode 100644 index 00000000000..1616c4db687 --- /dev/null +++ b/gnovm/tests/files/types/or_b1.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// one untyped const, one typed const +func main() { + println(1 | Error(1)) + println(Error(1) | 1) +} + +// Output: +// error: 1 +// error: 1 diff --git a/gnovm/tests/files/types/or_b2.gno b/gnovm/tests/files/types/or_b2.gno new file mode 100644 index 00000000000..88f6178a164 --- /dev/null +++ b/gnovm/tests/files/types/or_b2.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 | "a") +} + +// Error: +// main/files/types/or_b2.gno:5:10: operator | not defined on: StringKind diff --git a/gnovm/tests/files/types/or_b3.gno b/gnovm/tests/files/types/or_b3.gno new file mode 100644 index 00000000000..77ba41a72c7 --- /dev/null +++ b/gnovm/tests/files/types/or_b3.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println("b" | "a") +} + +// Error: +// main/files/types/or_b3.gno:5:10: operator | not defined on: StringKind diff --git a/gnovm/tests/files/types/or_b4.gno b/gnovm/tests/files/types/or_b4.gno new file mode 100644 index 00000000000..54c50033ad1 --- /dev/null +++ b/gnovm/tests/files/types/or_b4.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 | 'a') +} + +// Output: +// 97 diff --git a/gnovm/tests/files/types/or_d0.gno b/gnovm/tests/files/types/or_d0.gno new file mode 100644 index 00000000000..771a0cbe9f7 --- /dev/null +++ b/gnovm/tests/files/types/or_d0.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation, and this should happen in process stage!!! +func main() { + println(1.0 | 1) +} + +// Error: +// main/files/types/or_d0.gno:6:10: operator | not defined on: BigdecKind diff --git a/gnovm/tests/files/types/or_d1.gno b/gnovm/tests/files/types/or_d1.gno new file mode 100644 index 00000000000..ac2458a96a2 --- /dev/null +++ b/gnovm/tests/files/types/or_d1.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println('a' | 'b') +} + +// Output: +// 99 diff --git a/gnovm/tests/files/types/or_d2.gno b/gnovm/tests/files/types/or_d2.gno new file mode 100644 index 00000000000..0812ff926e7 --- /dev/null +++ b/gnovm/tests/files/types/or_d2.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// TODO: dec value representation +var r rune + +func main() { + r = 'a' + println(r | 'b') +} + +// Output: +// 99 diff --git a/gnovm/tests/files/types/or_d3.gno b/gnovm/tests/files/types/or_d3.gno new file mode 100644 index 00000000000..226c9423da5 --- /dev/null +++ b/gnovm/tests/files/types/or_d3.gno @@ -0,0 +1,15 @@ +package main + +// both untyped const +// TODO: dec value representation +var r1 rune +var r2 rune + +func main() { + r1 = 'a' + r2 = 'b' + println(r1 | r2) +} + +// Output: +// 99 diff --git a/gnovm/tests/files/types/or_d4.gno b/gnovm/tests/files/types/or_d4.gno new file mode 100644 index 00000000000..9a1fc007b66 --- /dev/null +++ b/gnovm/tests/files/types/or_d4.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println(1.0 | 0) +} + +// Error: +// main/files/types/or_d4.gno:6:10: operator | not defined on: BigdecKind diff --git a/gnovm/tests/files/types/or_e0.gno b/gnovm/tests/files/types/or_e0.gno new file mode 100644 index 00000000000..294e4bfbefb --- /dev/null +++ b/gnovm/tests/files/types/or_e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 | e2) +} + +// Error: +// main/files/types/or_e0.gno:23:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/or_f0_stdlibs.gno b/gnovm/tests/files/types/or_f0_stdlibs.gno new file mode 100644 index 00000000000..8e6fb54772a --- /dev/null +++ b/gnovm/tests/files/types/or_f0_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(1 | errCmp) +} + +// Error: +// main/files/types/or_f0_stdlibs.gno:19:10: operator | not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/or_f1_stdlibs.gno b/gnovm/tests/files/types/or_f1_stdlibs.gno new file mode 100644 index 00000000000..5013126c9fa --- /dev/null +++ b/gnovm/tests/files/types/or_f1_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(Error(0) | errCmp) +} + +// Error: +// main/files/types/or_f1_stdlibs.gno:19:10: operator | not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/or_f2.gno b/gnovm/tests/files/types/or_f2.gno new file mode 100644 index 00000000000..893025062bf --- /dev/null +++ b/gnovm/tests/files/types/or_f2.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 | e2) +} + +// Error: +// main/files/types/or_f2.gno:27:10: operator | not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/overflow_a0.gno b/gnovm/tests/files/types/overflow_a0.gno new file mode 100644 index 00000000000..e3ecefd621f --- /dev/null +++ b/gnovm/tests/files/types/overflow_a0.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const Huge = 1e1000 + fmt.Println(Huge / 1e999) +} + +// Output: +// 10 diff --git a/gnovm/tests/files/types/overflow_a1.gno b/gnovm/tests/files/types/overflow_a1.gno new file mode 100644 index 00000000000..bd893312cf2 --- /dev/null +++ b/gnovm/tests/files/types/overflow_a1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const Huge = 1e1000 + println(Huge) +} + +// Error: +// cannot convert untyped bigdec to float64: strconv.ParseFloat: parsing "1E+1000": value out of range diff --git a/gnovm/tests/files/types/rem_a0.gno b/gnovm/tests/files/types/rem_a0.gno new file mode 100644 index 00000000000..52fd7f9e8d1 --- /dev/null +++ b/gnovm/tests/files/types/rem_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) % int8(1)) +} + +// Error: +// main/files/types/rem_a0.gno:5:10: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/rem_a1.gno b/gnovm/tests/files/types/rem_a1.gno new file mode 100644 index 00000000000..5b43f854ba0 --- /dev/null +++ b/gnovm/tests/files/types/rem_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) % Error2(0)) +} + +// Error: +// main/files/types/rem_a1.gno:21:10: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/rem_a2.gno b/gnovm/tests/files/types/rem_a2.gno new file mode 100644 index 00000000000..26ce6599269 --- /dev/null +++ b/gnovm/tests/files/types/rem_a2.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(1 % 0) +} + +// Error: +// main/files/types/rem_a2.gno:4:10: invalid operation: division by zero diff --git a/gnovm/tests/files/types/rem_b0.gno b/gnovm/tests/files/types/rem_b0.gno new file mode 100644 index 00000000000..9b5f82fb3b7 --- /dev/null +++ b/gnovm/tests/files/types/rem_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 % Error(1)) + println(Error(1) % 1) +} + +// Output: +// (0 main.Error) +// (0 main.Error) diff --git a/gnovm/tests/files/types/rem_f3.gno b/gnovm/tests/files/types/rem_f3.gno new file mode 100644 index 00000000000..3d258975561 --- /dev/null +++ b/gnovm/tests/files/types/rem_f3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := 1 + println(a % 0) +} + +// Error: +// main/files/types/rem_f3.gno:5:10: invalid operation: division by zero diff --git a/gnovm/tests/files/types/runtime_a0.gno b/gnovm/tests/files/types/runtime_a0.gno new file mode 100644 index 00000000000..7a3fcf3e5a2 --- /dev/null +++ b/gnovm/tests/files/types/runtime_a0.gno @@ -0,0 +1,9 @@ +package main + +func main() { + m := map[string]bool{"foo": true} + m["foo"]++ +} + +// Error: +// main/files/types/runtime_a0.gno:5:2: operator ++ not defined on: BoolKind diff --git a/gnovm/tests/files/types/runtime_a0a.gno b/gnovm/tests/files/types/runtime_a0a.gno new file mode 100644 index 00000000000..fecce01817e --- /dev/null +++ b/gnovm/tests/files/types/runtime_a0a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(map[string]bool{"foo": true}["foo"] == true) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/runtime_a2.gno b/gnovm/tests/files/types/runtime_a2.gno new file mode 100644 index 00000000000..d53c9c4d970 --- /dev/null +++ b/gnovm/tests/files/types/runtime_a2.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func gen() interface{} { + return false +} + +func main() { + r := gen() + fmt.Printf("%T \n", r) +} + +// Output: +// bool diff --git a/gnovm/tests/files/types/runtime_a3.gno b/gnovm/tests/files/types/runtime_a3.gno new file mode 100644 index 00000000000..4e319021f33 --- /dev/null +++ b/gnovm/tests/files/types/runtime_a3.gno @@ -0,0 +1,12 @@ +package main + +func gen() interface{} { + return false +} + +func main() { + gen()++ +} + +// Error: +// main/files/types/runtime_a3.gno:8:2: operator ++ not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/shift_a0.gno b/gnovm/tests/files/types/shift_a0.gno new file mode 100644 index 00000000000..a31e7bd8f82 --- /dev/null +++ b/gnovm/tests/files/types/shift_a0.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(int(1) << int(1)) + println(int(1) >> int(1)) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a1.gno b/gnovm/tests/files/types/shift_a1.gno new file mode 100644 index 00000000000..ba92bf0f7d2 --- /dev/null +++ b/gnovm/tests/files/types/shift_a1.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(int(1) << int8(1)) + println(int(1) >> int8(1)) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a10.gno b/gnovm/tests/files/types/shift_a10.gno new file mode 100644 index 00000000000..0eba81f4a9c --- /dev/null +++ b/gnovm/tests/files/types/shift_a10.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + r := a << 1.0 // NOTE: go vet would fail, but still process + println(r) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/shift_a11.gno b/gnovm/tests/files/types/shift_a11.gno new file mode 100644 index 00000000000..2fbcb86b682 --- /dev/null +++ b/gnovm/tests/files/types/shift_a11.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + r := a << "hello" // NOTE: go vet would fail, but still process + println(r) +} + +// Error: +// main/files/types/shift_a11.gno:3:1: cannot convert StringKind to UintKind diff --git a/gnovm/tests/files/types/shift_a12.gno b/gnovm/tests/files/types/shift_a12.gno new file mode 100644 index 00000000000..5735854d684 --- /dev/null +++ b/gnovm/tests/files/types/shift_a12.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func main() { + a := uint(1) + r := uint64(1) << a + println(r) + fmt.Printf("%T \n", r) +} + +// Output: +// 2 +// uint64 diff --git a/gnovm/tests/files/types/shift_a13.gno b/gnovm/tests/files/types/shift_a13.gno new file mode 100644 index 00000000000..7d70cc3589a --- /dev/null +++ b/gnovm/tests/files/types/shift_a13.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + var r uint64 = 1 << int8(1) + fmt.Printf("%T \n", r) + println(r) +} + +// Output: +// uint64 +// 2 diff --git a/gnovm/tests/files/types/shift_a14.gno b/gnovm/tests/files/types/shift_a14.gno new file mode 100644 index 00000000000..60fc10211de --- /dev/null +++ b/gnovm/tests/files/types/shift_a14.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := "hello" + r := a << 1 + println(r) +} + +// Error: +// main/files/types/shift_a14.gno:5:7: operator << not defined on: StringKind diff --git a/gnovm/tests/files/types/shift_a15.gno b/gnovm/tests/files/types/shift_a15.gno new file mode 100644 index 00000000000..58f6814bb12 --- /dev/null +++ b/gnovm/tests/files/types/shift_a15.gno @@ -0,0 +1,10 @@ +package main + +// TODO: fix in assignment +func main() { + a := "hello" + a <<= 1 +} + +// Error: +// main/files/types/shift_a15.gno:6:2: operator <<= not defined on: StringKind diff --git a/gnovm/tests/files/types/shift_a16.gno b/gnovm/tests/files/types/shift_a16.gno new file mode 100644 index 00000000000..d3b15be5427 --- /dev/null +++ b/gnovm/tests/files/types/shift_a16.gno @@ -0,0 +1,9 @@ +package main + +func main() { + r := "hello" << 1 + println(r) +} + +// Error: +// main/files/types/shift_a16.gno:4:7: operator << not defined on: StringKind diff --git a/gnovm/tests/files/types/shift_a2.gno b/gnovm/tests/files/types/shift_a2.gno new file mode 100644 index 00000000000..91072929306 --- /dev/null +++ b/gnovm/tests/files/types/shift_a2.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(1 << int(1)) + println(1 >> int(1)) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a3.gno b/gnovm/tests/files/types/shift_a3.gno new file mode 100644 index 00000000000..32780e2724a --- /dev/null +++ b/gnovm/tests/files/types/shift_a3.gno @@ -0,0 +1,10 @@ +package main + +// both typed(different) const +func main() { + println(1 << 'a') + println(1 >> 'a') +} + +// Error: +// main/files/types/shift_a3.gno:5:2: bigint overflows target kind diff --git a/gnovm/tests/files/types/shift_a4.gno b/gnovm/tests/files/types/shift_a4.gno new file mode 100644 index 00000000000..3561929b672 --- /dev/null +++ b/gnovm/tests/files/types/shift_a4.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(1 << 1.0) + println(1 >> 1.0) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a5.gno b/gnovm/tests/files/types/shift_a5.gno new file mode 100644 index 00000000000..a0b7652c6d1 --- /dev/null +++ b/gnovm/tests/files/types/shift_a5.gno @@ -0,0 +1,10 @@ +package main + +// TODO: support this? +func main() { + println(1.0 << 1) + println(1.0 >> 1) +} + +// Error: +// main/files/types/shift_a5.gno:5:10: operator << not defined on: BigdecKind diff --git a/gnovm/tests/files/types/shift_a6.gno b/gnovm/tests/files/types/shift_a6.gno new file mode 100644 index 00000000000..03ad4c0bcea --- /dev/null +++ b/gnovm/tests/files/types/shift_a6.gno @@ -0,0 +1,14 @@ +package main + +func main() { + r := int(1) + println(r << 5) + println(r) + r <<= 5 + println(r) +} + +// Output: +// 32 +// 1 +// 32 diff --git a/gnovm/tests/files/types/shift_a7.gno b/gnovm/tests/files/types/shift_a7.gno new file mode 100644 index 00000000000..62151c5cfc5 --- /dev/null +++ b/gnovm/tests/files/types/shift_a7.gno @@ -0,0 +1,9 @@ +package main + +func main() { + r := int(1) + println(r << "a") +} + +// Error: +// main/files/types/shift_a7.gno:3:1: cannot convert StringKind to UintKind diff --git a/gnovm/tests/files/types/shift_a8.gno b/gnovm/tests/files/types/shift_a8.gno new file mode 100644 index 00000000000..a7a3e1f07d9 --- /dev/null +++ b/gnovm/tests/files/types/shift_a8.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + r := a << 1 + println(r) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/shift_a9.gno b/gnovm/tests/files/types/shift_a9.gno new file mode 100644 index 00000000000..f998381ef1c --- /dev/null +++ b/gnovm/tests/files/types/shift_a9.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a <<= 1 + println(a) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/time_native.gno b/gnovm/tests/files/types/time_native.gno new file mode 100644 index 00000000000..ef1c3daaf44 --- /dev/null +++ b/gnovm/tests/files/types/time_native.gno @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + fmt.Println(time.Second + time.Second) +} + +// Output: +// 2s diff --git a/gnovm/tests/files/types/unary_a0.gno b/gnovm/tests/files/types/unary_a0.gno new file mode 100644 index 00000000000..2e5a1d289c9 --- /dev/null +++ b/gnovm/tests/files/types/unary_a0.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 5 + y := +x + fmt.Println(y) // Output: 5 +} + +// Output: +// 5 diff --git a/gnovm/tests/files/types/unary_a0a.gno b/gnovm/tests/files/types/unary_a0a.gno new file mode 100644 index 00000000000..3556bcf2bf6 --- /dev/null +++ b/gnovm/tests/files/types/unary_a0a.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 1.0 + y := +x + fmt.Println(y) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/unary_a0b.gno b/gnovm/tests/files/types/unary_a0b.gno new file mode 100644 index 00000000000..ff4e863e10e --- /dev/null +++ b/gnovm/tests/files/types/unary_a0b.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := int(1) + y := +x + fmt.Println(y) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/unary_a0c.gno b/gnovm/tests/files/types/unary_a0c.gno new file mode 100644 index 00000000000..b782898f62d --- /dev/null +++ b/gnovm/tests/files/types/unary_a0c.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := "hello" + y := +x + fmt.Println(y) +} + +// Error: +// main/files/types/unary_a0c.gno:7:7: operator + not defined on: StringKind diff --git a/gnovm/tests/files/types/unary_a1.gno b/gnovm/tests/files/types/unary_a1.gno new file mode 100644 index 00000000000..c3c96d6724e --- /dev/null +++ b/gnovm/tests/files/types/unary_a1.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + x := 5 + y := -x + fmt.Println(y) // Output: -5 + +} + +// Output: +// -5 diff --git a/gnovm/tests/files/types/unary_a2.gno b/gnovm/tests/files/types/unary_a2.gno new file mode 100644 index 00000000000..1b9edf1b712 --- /dev/null +++ b/gnovm/tests/files/types/unary_a2.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + a := true + b := !a + fmt.Println(b) // Output: false + +} + +// Output: +// false diff --git a/gnovm/tests/files/types/unary_a2a.gno b/gnovm/tests/files/types/unary_a2a.gno new file mode 100644 index 00000000000..3bd80c7944d --- /dev/null +++ b/gnovm/tests/files/types/unary_a2a.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + a := 1 + b := !a + fmt.Println(b) // Output: false + +} + +// Error: +// main/files/types/unary_a2a.gno:7:7: operator ! not defined on: IntKind diff --git a/gnovm/tests/files/types/unary_a3.gno b/gnovm/tests/files/types/unary_a3.gno new file mode 100644 index 00000000000..3f216ccb1c1 --- /dev/null +++ b/gnovm/tests/files/types/unary_a3.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 10 + p := &x + fmt.Println(*p) // Output: 10 +} + +// Output: +// 10 diff --git a/gnovm/tests/files/types/unary_a4.gno b/gnovm/tests/files/types/unary_a4.gno new file mode 100644 index 00000000000..caaf1ed0438 --- /dev/null +++ b/gnovm/tests/files/types/unary_a4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + v := 42 + p := &v + println(v == *p) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/unary_a5.gno b/gnovm/tests/files/types/unary_a5.gno new file mode 100644 index 00000000000..bb956f85673 --- /dev/null +++ b/gnovm/tests/files/types/unary_a5.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 1 + y := ^x + fmt.Println(y) // Output will be the bitwise complement of 1 +} + +// Output: +// -2 diff --git a/gnovm/tests/files/types/unary_a6.gno b/gnovm/tests/files/types/unary_a6.gno new file mode 100644 index 00000000000..3a27d1c2745 --- /dev/null +++ b/gnovm/tests/files/types/unary_a6.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 1.0 + y := ^x + fmt.Println(y) // Output will be the bitwise complement of 1 +} + +// Error: +// main/files/types/unary_a6.gno:7:7: operator ^ not defined on: Float64Kind diff --git a/gnovm/tests/files/var18.gno b/gnovm/tests/files/var18.gno new file mode 100644 index 00000000000..771ddcdffb5 --- /dev/null +++ b/gnovm/tests/files/var18.gno @@ -0,0 +1,8 @@ +package main + +func main() { + a, b, c := 1, 2 +} + +// Error: +// main/files/var18.gno:4:2: assignment mismatch: 3 variables but 2 values diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index 7578781e503..f2b7bf663c3 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -33,14 +33,21 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { // "T": { // "@type": "/gno.FuncType", // "Params": [], @@ -63,9 +70,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "6", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -75,13 +82,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index d90c5e8621a..469dfff5d5a 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -60,15 +60,26 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.InnerNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ae4e9e2d205cc0081d4ee249e1d188ebe270b220", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// }, +// { +// "T": { // "@type": "/gno.TypeType" // }, // "V": { @@ -177,9 +188,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "17", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -189,17 +200,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.InnerNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ae4e9e2d205cc0081d4ee249e1d188ebe270b220", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm10.gno b/gnovm/tests/files/zrealm10.gno index 97bb9d439e2..110567bfb12 100644 --- a/gnovm/tests/files/zrealm10.gno +++ b/gnovm/tests/files/zrealm10.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "Fields": [ // { // "N": "AwAAAAAAAAA=", @@ -34,7 +34,7 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", // "ModTime": "4", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" diff --git a/gnovm/tests/files/zrealm11.gno b/gnovm/tests/files/zrealm11.gno index 0f4d26a44c0..7c8805551c9 100644 --- a/gnovm/tests/files/zrealm11.gno +++ b/gnovm/tests/files/zrealm11.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "Fields": [ // { // "N": "//////////8=", @@ -34,7 +34,7 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", // "ModTime": "4", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" diff --git a/gnovm/tests/files/zrealm12.gno b/gnovm/tests/files/zrealm12_stdlibs.gno similarity index 55% rename from gnovm/tests/files/zrealm12.gno rename to gnovm/tests/files/zrealm12_stdlibs.gno index 0049cba6073..d201ecc1470 100644 --- a/gnovm/tests/files/zrealm12.gno +++ b/gnovm/tests/files/zrealm12_stdlibs.gno @@ -2,29 +2,25 @@ package test import ( - "std" - "gno.land/r/demo/tests" + "std" ) func main() { - if std.TestCurrentRealm() != "gno.land/r/test" { - panic("should not happen") - } tests.InitTestNodes() - if std.TestCurrentRealm() != "gno.land/r/test" { + if std.CurrentRealm().PkgPath() != "gno.land/r/test" { panic("should not happen") } tests.ModTestNodes() - if std.TestCurrentRealm() != "gno.land/r/test" { + if std.CurrentRealm().PkgPath() != "gno.land/r/test" { panic("should not happen") } std.ClearStoreCache() - if std.TestCurrentRealm() != "gno.land/r/test" { + if std.CurrentRealm().PkgPath() != "gno.land/r/test" { panic("should not happen") } tests.PrintTestNodes() - if std.TestCurrentRealm() != "gno.land/r/test" { + if std.CurrentRealm().PkgPath() != "gno.land/r/test" { panic("should not happen") } } diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index 67ba2f5a768..00757e46867 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -63,15 +63,26 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.InnerNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "61d4aa77a87c01e07038c6030d6aca299d0fdc1b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// }, +// { +// "T": { // "@type": "/gno.TypeType" // }, // "V": { @@ -172,7 +183,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.3", +// "Name": "init.4", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -180,9 +191,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "17", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -216,9 +227,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "23", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -228,17 +239,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.InnerNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "61d4aa77a87c01e07038c6030d6aca299d0fdc1b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index da8a581375c..a519c67c495 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -25,7 +25,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -57,10 +57,29 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "567a18d9c7594ece7956ce54384b0858888bb834", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -68,7 +87,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -76,15 +95,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "8197b7c5b4f2c7bf9c12b1c614f6b4dc6e7ce8dd", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.TypeType" // }, // "V": { @@ -171,7 +209,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.2", +// "Name": "init.3", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -179,9 +217,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "14", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -215,9 +253,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "20", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -227,32 +265,8 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "b1d00c9606ffbb00b2aa3d475c5a390514f6dc14", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } -// } // } // ] // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index 4f5254b6951..551b3dff095 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -72,9 +72,9 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "4", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } @@ -83,7 +83,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -91,15 +91,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -113,7 +132,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -121,9 +140,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "11", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -157,9 +176,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "15", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -169,31 +188,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "de0c4b2dd935220f7d37d10fc9feb1448bfb011d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index ebe107290e7..8062d1bde6d 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -72,13 +72,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8a86634afa28ef7d7a1f4272255637f16daae2cd", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -126,26 +145,20 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7c63a8fd451cd7c470c1851f1ead037246422ded", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "1a4158d473290431f9d4f9c5a85a3b6697640f2a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "4", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } @@ -154,7 +167,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -162,15 +175,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -184,7 +216,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -192,9 +224,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "11", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -228,9 +260,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "15", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -240,31 +272,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "011a2960ff92aedda8acd122b9f4d251902e0cd8", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index 9884ab1909c..9fb36c64a14 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -73,13 +73,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "34a46349a2bc1b58591d0222a145b585452683be", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -127,30 +146,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "81074f5da453299a913435a2ddd05248ee012f8c", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "b1719c55a9b07d432385f020b0bdbc678ba2b9ac", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -198,26 +211,20 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7c63a8fd451cd7c470c1851f1ead037246422ded", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "8d00c3fa6c15cb0d78dcbaa23df49f96bbc9591b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } @@ -226,7 +233,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "5", +// "ModTime": "7", // "RefCount": "2" // }, // "Parent": null, @@ -234,15 +241,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ade9fce2a987ef1924040a1d75c0172410c66952", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -256,7 +282,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -264,9 +290,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "11", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -300,9 +326,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "16", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -312,31 +338,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "8f2cb2a771ddc55ab5798b791e16df547c94d862", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index a706ffcad78..7e19851cd13 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -25,7 +25,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ // "Fields": [ // { // "T": { @@ -74,13 +74,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "42cd813e173ad23c7873e9605901e8bea1176c96", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -128,30 +147,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "4f88fcdc73a4a94905e8e4044aa50c2ec7bf2227", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "2ac7cc7e6fdb1ff6dc1f340486011f1449757d85", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", -// "ModTime": "6", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2c172bbe0183ccc73c59d9acb196c45b0331c39e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -160,7 +192,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key0" +// "value": "key1" // } // }, // { @@ -170,11 +202,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value0" +// "value": "value1" // } // }, // { -// "N": "AQAAAAAAAAA=", +// "N": "AwAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -187,6 +219,16 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "a4fa9bdf45caf8c6b5be7a3752704423817b3ef2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null // } // }, // { @@ -196,13 +238,23 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "43f69f24b7827a331921b4af0f667346d186e0c3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, +// "Index": "0", +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "6", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "RefCount": "1" // } // } @@ -215,7 +267,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key1" +// "value": "key0" // } // }, // { @@ -225,11 +277,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value1" +// "value": "value0" // } // }, // { -// "N": "AwAAAAAAAAA=", +// "N": "AQAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -242,22 +294,6 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "a0af92becf7bef8d5d71c94e8f8f044e4cfe526d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } // } // }, // { @@ -267,38 +303,41 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "752161efcfe5a3e2ef70c03ff4354097f09ada56", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } // } // } // ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", -// "ModTime": "6", +// "ModTime": "9", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f56fbd9c8db299689cc0cf806fe741b6a6e641e6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ // "Blank": {}, // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "6", +// "ModTime": "9", // "RefCount": "2" // }, // "Parent": null, @@ -306,15 +345,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "450aef9858564ed4ec1c418f1e8dac828079016b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -328,7 +386,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -336,9 +394,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "11", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -372,9 +430,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "17", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -384,31 +442,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c59d05a21bf190551bb15a8b9d41a9e8da717f3d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm8.gno b/gnovm/tests/files/zrealm8.gno index 1452301dac6..2b8e946509c 100644 --- a/gnovm/tests/files/zrealm8.gno +++ b/gnovm/tests/files/zrealm8.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "Fields": [ // { // "N": "AgAAAAAAAAA=", @@ -34,7 +34,7 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", // "ModTime": "4", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" diff --git a/gnovm/tests/files/zrealm9.gno b/gnovm/tests/files/zrealm9.gno index 306ca02a92a..4ddf67d018e 100644 --- a/gnovm/tests/files/zrealm9.gno +++ b/gnovm/tests/files/zrealm9.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "Fields": [ // { // "T": { @@ -33,7 +33,7 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", // "ModTime": "4", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index e91788ac8eb..aff79ffabc6 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -25,67 +25,25 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "key0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "value0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -140,13 +98,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -183,19 +160,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "6da365f0d6cacbcdf53cd5a4b125803cddce08c2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "091729e38bda8724bce4c314f9624b91af679459", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // }, // { @@ -208,27 +179,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "f216afe7b5a17f4ebdbb98dceccedbc22e237596", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ff1a50d8489090af37a2c7766d659f0d717939b5", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -236,7 +220,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -244,15 +228,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -266,7 +269,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -274,9 +277,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "10", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -310,9 +313,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "15", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -322,31 +325,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6c9948281d4c60b2d95233b76388d54d8b1a2fad", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } -// } // } // ] // } diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index cdd56a5ad89..3b6d40d5ecd 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { // "T": { @@ -79,13 +79,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "143aebc820da33550f7338723fb1e2eec575b196", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={ // "Fields": [ // { // "T": { @@ -122,19 +141,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "7a8a63e17a567d7b0891ac89d5cd90072a73787d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // }, // { @@ -147,30 +160,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2e733a8e9e74fe14f0a5d10fb0f6728fa53d052d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ab5a297f4eb033d88bdf1677f4dc151ccb9fde9f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ // "Fields": [ // { // "T": { @@ -207,19 +233,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // }, // { @@ -232,27 +252,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fe8afd501233fb95375016199f0443b3c6ab1fbc", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -260,7 +293,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "6", +// "ModTime": "9", // "RefCount": "2" // }, // "Parent": null, @@ -268,15 +301,34 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, // "Values": [ // { // "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -290,7 +342,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "init.0", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -298,9 +350,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "10", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -334,9 +386,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", // "Line": "15", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -346,32 +398,8 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c5eefc40ed065461b4a920c1349ed734ffdead8f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } -// } // } // ] // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm_avl2.gno b/gnovm/tests/files/zrealm_avl2.gno index 8ecf6ba52bc..b861413feca 100644 --- a/gnovm/tests/files/zrealm_avl2.gno +++ b/gnovm/tests/files/zrealm_avl2.gno @@ -24,67 +24,25 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "key1" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "value1" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", -// "ModTime": "6", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ // "Fields": [ // { // "T": { @@ -139,13 +97,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "be751422ef4c2bc068a456f9467d2daca27db8ca", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ // "Fields": [ // { // "T": { @@ -182,19 +159,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "9fa04d8791e205a6de2eedce81bb4dbd0883cac7", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "23c8d928ce614d559719cb47e71a75a456b49a2a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // }, // { @@ -207,30 +178,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "a55a6a6b2027d6ec5e322aa32d4269b974fe1a4f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fc92b122743503329a416d02fb4fe84cbca6dc57", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "03d901636a4e56d5bd32a75a7b923c7700c8859a", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "Fields": [ // { // "T": { @@ -242,25 +226,19 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "953a28a33bf1bae93eb1fcb4d1b348ccabcbaabd", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "e6d40c7e6f2c94668ab964b4c356d7cbd537a2b5", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "ModTime": "6", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" // } diff --git a/gnovm/tests/files/zrealm_const.gno b/gnovm/tests/files/zrealm_const_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_const.gno rename to gnovm/tests/files/zrealm_const_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm0.gno b/gnovm/tests/files/zrealm_crossrealm0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm0.gno rename to gnovm/tests/files/zrealm_crossrealm0_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm10.gno b/gnovm/tests/files/zrealm_crossrealm10_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm10.gno rename to gnovm/tests/files/zrealm_crossrealm10_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11_stdlibs.gno similarity index 99% rename from gnovm/tests/files/zrealm_crossrealm11.gno rename to gnovm/tests/files/zrealm_crossrealm11_stdlibs.gno index b250b07bbac..e6f33c50654 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11_stdlibs.gno @@ -2,13 +2,10 @@ package crossrealm_test import ( - "std" - "strings" - ptests "gno.land/p/demo/tests" "gno.land/p/demo/ufmt" rtests "gno.land/r/demo/tests" - testfoo "gno.land/r/demo/tests_foo" + "std" ) func getPrevRealm() std.Realm { diff --git a/gnovm/tests/files/zrealm_crossrealm12.gno b/gnovm/tests/files/zrealm_crossrealm12_stdlibs.gno similarity index 86% rename from gnovm/tests/files/zrealm_crossrealm12.gno rename to gnovm/tests/files/zrealm_crossrealm12_stdlibs.gno index ef8ea141ac5..f2f229cd5de 100644 --- a/gnovm/tests/files/zrealm_crossrealm12.gno +++ b/gnovm/tests/files/zrealm_crossrealm12_stdlibs.gno @@ -2,22 +2,22 @@ package crossrealm_test import ( - "std" "fmt" + "std" psubtests "gno.land/p/demo/tests/subtests" rsubtests "gno.land/r/demo/tests/subtests" ) func main() { - tests := []struct{ + tests := []struct { fn func() std.Realm }{ - { std.CurrentRealm }, - { psubtests.GetCurrentRealm }, - { rsubtests.GetCurrentRealm }, + {std.CurrentRealm}, + {psubtests.GetCurrentRealm}, + {rsubtests.GetCurrentRealm}, } - + for _, test := range tests { r := test.fn() diff --git a/gnovm/tests/files/zrealm_crossrealm13_stdlibs.gno b/gnovm/tests/files/zrealm_crossrealm13_stdlibs.gno new file mode 100644 index 00000000000..4daeb6de366 --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm13_stdlibs.gno @@ -0,0 +1,48 @@ +package main + +import ( + "std" +) + +func main() { + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) + 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")) + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) +} + +func pad(s string) string { + for len(s) < 26 { + s += " " + } + return s +} + +func PrintRealm() { + println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) + println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) +} + +// Because this is the context of a package, using PrintRealm() +// should not change the output of the main function. + +// Output: +// PrintRealm: CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrevRealm: (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) +// 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) diff --git a/gnovm/tests/files/zrealm_crossrealm13a_stdlibs.gno b/gnovm/tests/files/zrealm_crossrealm13a_stdlibs.gno new file mode 100644 index 00000000000..2fc37804fce --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm13a_stdlibs.gno @@ -0,0 +1,46 @@ +// PKGPATH: gno.land/r/demo/groups +package groups + +import ( + "std" +) + +func main() { + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) + 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")) + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) +} + +func pad(s string) string { + for len(s) < 26 { + s += " " + } + return s +} + +func PrintRealm() { + println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) + println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) +} + +// Output: +// PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) +// PrintRealm: PrevRealm: (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) +// PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrevRealm: (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) diff --git a/gnovm/tests/files/zrealm_crossrealm14.gno b/gnovm/tests/files/zrealm_crossrealm14.gno new file mode 100644 index 00000000000..23451e6f5d1 --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm14.gno @@ -0,0 +1,17 @@ +// PKGPATH: gno.land/r/crossrealm_test +package crossrealm_test + +import ( + crossrealm "gno.land/r/demo/tests/crossrealm" +) + +func main() { + // even though we are running within a realm, + // we aren't storing the result of crossrealm.Make1(), + // so this should print fine. + crossrealm.Make1().Touch().Print() +} + +// Output: +// A: 2 +// B: LocalStruct{123} diff --git a/gnovm/tests/files/zrealm_crossrealm1.gno b/gnovm/tests/files/zrealm_crossrealm1_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm1.gno rename to gnovm/tests/files/zrealm_crossrealm1_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm2.gno b/gnovm/tests/files/zrealm_crossrealm2_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm2.gno rename to gnovm/tests/files/zrealm_crossrealm2_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm3.gno b/gnovm/tests/files/zrealm_crossrealm3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm3.gno rename to gnovm/tests/files/zrealm_crossrealm3_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm4.gno b/gnovm/tests/files/zrealm_crossrealm4_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm4.gno rename to gnovm/tests/files/zrealm_crossrealm4_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm5.gno b/gnovm/tests/files/zrealm_crossrealm5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm5.gno rename to gnovm/tests/files/zrealm_crossrealm5_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm6.gno b/gnovm/tests/files/zrealm_crossrealm6_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm6.gno rename to gnovm/tests/files/zrealm_crossrealm6_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm7.gno b/gnovm/tests/files/zrealm_crossrealm7_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm7.gno rename to gnovm/tests/files/zrealm_crossrealm7_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm8.gno b/gnovm/tests/files/zrealm_crossrealm8_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm8.gno rename to gnovm/tests/files/zrealm_crossrealm8_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm9.gno b/gnovm/tests/files/zrealm_crossrealm9_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm9.gno rename to gnovm/tests/files/zrealm_crossrealm9_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_example.gno b/gnovm/tests/files/zrealm_example.gno index 1ce70c04390..45aeb7c5ddb 100644 --- a/gnovm/tests/files/zrealm_example.gno +++ b/gnovm/tests/files/zrealm_example.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/example"] -// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:8]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:11]={ // "Fields": [ // { // "T": { @@ -37,13 +37,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:11", // "ModTime": "0", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:10", // "RefCount": "1" // } // } -// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:7]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:10]={ // "Fields": [ // { // "T": { @@ -72,19 +72,38 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "c3dc30d2f2a57a0eeb4336dae59355aa7bee0ff5", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8" +// "Hash": "f190df54e397e2006cee3fc525bcc1b4d556e4c4", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:11" // } // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:10", // "ModTime": "0", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:9", // "RefCount": "1" // } // } -// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:6]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:9]={ +// "ObjectInfo": { +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:9", +// "ModTime": "0", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/dom.Post" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a74fad6da10f1cec74ad3a8751490b4dca957761", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:10" +// } +// } +// } +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:8]={ // "Fields": [ // { // "T": { @@ -106,19 +125,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "8b11b3d07ddeb034f70a114c9433ec6bd5cbf899", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:9" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/dom.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "f5d48c5a050326190d971fabb76835de31f83b20", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7" -// } -// } +// "TV": null // } // }, // { @@ -154,13 +167,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8", // "ModTime": "0", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", // "RefCount": "1" // } // } -// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:5]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:7]={ +// "ObjectInfo": { +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", +// "ModTime": "0", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "869abdac30a3ae78b2191806e1c894c48e399122", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8" +// } +// } +// } +// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:6]={ // "Fields": [ // { // "T": { @@ -172,30 +204,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "a919087d0eba652876f9a8df18b30ec5ddc8c26e", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "e373f3e5c834170fe6e8b6cf5a95d185e80b0ad7", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", -// "ModTime": "5", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:4", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "ModTime": "6", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", // "RefCount": "1" // } // } -// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:4]={ +// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:5]={ // "Fields": [ // { // "T": { @@ -214,8 +240,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "05c2d0709574f676715a23d0161d2e151c0b21c7", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5" +// "Hash": "dfdeb7ed80c5b030c3a5e9701d00c66203de6f57", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6" // } // }, // { @@ -227,9 +253,9 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:4", -// "ModTime": "5", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:2", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", +// "ModTime": "6", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:4", // "RefCount": "1" // } // } diff --git a/gnovm/tests/files/zrealm_initctx.gno b/gnovm/tests/files/zrealm_initctx_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_initctx.gno rename to gnovm/tests/files/zrealm_initctx_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0_stdlibs.gno similarity index 91% rename from gnovm/tests/files/zrealm_natbind0.gno rename to gnovm/tests/files/zrealm_natbind0_stdlibs.gno index 084ddd3d18f..c852f4a09f7 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0_stdlibs.gno @@ -12,19 +12,19 @@ func init() { } func main() { - // NOTE: this test uses GetHeight and CurrentRealmPath, which are "pure" + // NOTE: this test uses GetHeight and GetChainID, 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.CurrentRealmPath + node = std.GetChainID g := node.(func() string) println(g()) } // Output: // 123 -// gno.land/r/test +// dev // Realm: // switchrealm["gno.land/r/test"] @@ -41,9 +41,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/test" // } // }, @@ -52,35 +52,55 @@ func main() { // "T": { // "@type": "/gno.FuncType", // "Params": [], -// "Results": [] +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ] // }, // "V": { // "@type": "/gno.FuncValue", // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:8" // }, -// "FileName": "main.gno", +// "FileName": "native.gno", // "IsMethod": false, -// "Name": "init.0", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", +// "Name": "GetChainID", +// "NativeName": "GetChainID", +// "NativePkg": "std", +// "PkgPath": "std", // "Source": { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { -// "File": "main.gno", -// "Line": "10", -// "Nonce": "0", -// "PkgPath": "gno.land/r/test" +// "Column": "1", +// "File": "native.gno", +// "Line": "13", +// "PkgPath": "std" // } // }, // "Type": { // "@type": "/gno.FuncType", // "Params": [], -// "Results": [] +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ] // } // } // }, @@ -99,7 +119,7 @@ func main() { // }, // "FileName": "main.gno", // "IsMethod": false, -// "Name": "main", +// "Name": "init.1", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/test", @@ -107,9 +127,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "main.gno", -// "Line": "14", -// "Nonce": "0", +// "Line": "10", // "PkgPath": "gno.land/r/test" // } // }, @@ -124,55 +144,35 @@ func main() { // "T": { // "@type": "/gno.FuncType", // "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] +// "Results": [] // }, // "V": { // "@type": "/gno.FuncValue", // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:7" +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" // }, -// "FileName": "native.gno", +// "FileName": "main.gno", // "IsMethod": false, -// "Name": "CurrentRealmPath", -// "NativeName": "CurrentRealmPath", -// "NativePkg": "std", -// "PkgPath": "std", +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { -// "File": "native.gno", -// "Line": "5", -// "Nonce": "0", -// "PkgPath": "std" +// "Column": "1", +// "File": "main.gno", +// "Line": "14", +// "PkgPath": "gno.land/r/test" // } // }, // "Type": { // "@type": "/gno.FuncType", // "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] +// "Results": [] // } // } // } diff --git a/gnovm/tests/files/zrealm_std0.gno b/gnovm/tests/files/zrealm_std0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std0.gno rename to gnovm/tests/files/zrealm_std0_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std1.gno b/gnovm/tests/files/zrealm_std1_stdlibs.gno similarity index 88% rename from gnovm/tests/files/zrealm_std1.gno rename to gnovm/tests/files/zrealm_std1_stdlibs.gno index 87f75bcb871..d75a2c60b71 100644 --- a/gnovm/tests/files/zrealm_std1.gno +++ b/gnovm/tests/files/zrealm_std1_stdlibs.gno @@ -25,7 +25,7 @@ func main() { } // Output: -// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:4)] std.AddressList) +// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:5)] std.AddressList) // error: address already exists // has: true // has: false diff --git a/gnovm/tests/files/zrealm_std2.gno b/gnovm/tests/files/zrealm_std2_stdlibs.gno similarity index 90% rename from gnovm/tests/files/zrealm_std2.gno rename to gnovm/tests/files/zrealm_std2_stdlibs.gno index 1ae1fb4a881..810210c6160 100644 --- a/gnovm/tests/files/zrealm_std2.gno +++ b/gnovm/tests/files/zrealm_std2_stdlibs.gno @@ -26,7 +26,7 @@ func main() { } // Output: -// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:4)] std.AddressList) +// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:5)] std.AddressList) // error: address already exists // has: true // has: false diff --git a/gnovm/tests/files/zrealm_std3.gno b/gnovm/tests/files/zrealm_std3_stdlibs.gno similarity index 65% rename from gnovm/tests/files/zrealm_std3.gno rename to gnovm/tests/files/zrealm_std3_stdlibs.gno index c13feffa42c..4f1d1bc827a 100644 --- a/gnovm/tests/files/zrealm_std3.gno +++ b/gnovm/tests/files/zrealm_std3_stdlibs.gno @@ -6,11 +6,11 @@ import ( ) func foo() { - println("foo", std.CurrentRealmPath()) + println("foo", std.CurrentRealm().PkgPath()) } func main() { - println("main", std.CurrentRealmPath()) + println("main", std.CurrentRealm().PkgPath()) foo() } diff --git a/gnovm/tests/files/zrealm_std4.gno b/gnovm/tests/files/zrealm_std4_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std4.gno rename to gnovm/tests/files/zrealm_std4_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std5.gno b/gnovm/tests/files/zrealm_std5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std5.gno rename to gnovm/tests/files/zrealm_std5_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std6.gno b/gnovm/tests/files/zrealm_std6_stdlibs.gno similarity index 56% rename from gnovm/tests/files/zrealm_std6.gno rename to gnovm/tests/files/zrealm_std6_stdlibs.gno index fb9e0f825a2..17a79c1d43b 100644 --- a/gnovm/tests/files/zrealm_std6.gno +++ b/gnovm/tests/files/zrealm_std6_stdlibs.gno @@ -1,5 +1,5 @@ -// PKGPATH: gno.land/r/test -package test +// PKGPATH: gno.land/r/std_test +package std_test import ( "std" @@ -14,4 +14,4 @@ func main() { } // Output: -// g148esskg7wzxxw6axwv4j5nkgjzdp6v4zmy3pwc +// g157y5v3k529jyzhjjz4fn49tzzhf4gess6v39xg diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0_stdlibs.gno similarity index 59% rename from gnovm/tests/files/zrealm_tests0.gno rename to gnovm/tests/files/zrealm_tests0_stdlibs.gno index 79a930b5ecc..d11701505e5 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0_stdlibs.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/tests"] -// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:10]={ +// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:18]={ // "Fields": [ // { // "T": { @@ -37,13 +37,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:10", +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:18", // "ModTime": "0", -// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17", // "RefCount": "1" // } // } -// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9]={ +// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17]={ +// "ObjectInfo": { +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17", +// "ModTime": "0", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests_foo.FooStringer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d3d6ffa52602f2bc976051d79294d219750aca64", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:18" +// } +// } +// } +// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16]={ // "Data": null, // "List": [ // { @@ -56,19 +75,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "4ea1e08156f3849b74a0f41f92cd4b48fb94926b", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:11" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests_foo.FooStringer" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "94c14b2efc4bb2f3c24ee42292f161fd1ebd72a3", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:6" -// } -// } +// "TV": null // } // }, // { @@ -81,19 +94,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ce86ea1156e75a44cd9d7ba2261819b100aa4ed1", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests_foo.FooStringer" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "1e36da78d1dc72e5cbac56c27590332574c89678", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" -// } -// } +// "TV": null // } // }, // { @@ -106,24 +113,18 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b66192fbd8a8dde79b6f854b5cc3c4cc965cfd92", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests_foo.FooStringer" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "43b13870b750f78cda919fa13a5d955d297242bd", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:10" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9", +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16", // "ModTime": "0", // "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2", // "RefCount": "1" @@ -134,17 +135,17 @@ func main() { // "ObjectInfo": { // "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2", // "IsEscaped": true, -// "ModTime": "8", -// "RefCount": "3" +// "ModTime": "15", +// "RefCount": "5" // }, // "Parent": null, // "Source": { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "0", // "File": "", // "Line": "0", -// "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -192,131 +193,23 @@ func main() { // }, // { // "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Field", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "tests.gno", -// "IsMethod": true, -// "Name": "Modify", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "File": "tests.gno", -// "Line": "48", -// "Nonce": "0", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } -// } -// ], -// "Name": "TestRealmObject", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" +// "@type": "/gno.SliceType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.Stringer" +// }, +// "Vrd": false // }, // "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Name", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// }, -// { -// "Embedded": false, -// "Name": "Child", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "TestNode", -// "PkgPath": "gno.land/r/demo/tests" -// } +// "@type": "/gno.SliceValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ad25f70f66c8c53042afd1377e5ff5ab744bf1a5", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16" +// }, +// "Length": "3", +// "Maxcap": "3", +// "Offset": "0" // } // }, // { @@ -352,9 +245,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "interfaces.gno", // "Line": "13", -// "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -418,9 +311,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "interfaces.gno", // "Line": "20", -// "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -453,108 +346,186 @@ func main() { // }, // { // "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] +// "@type": "/gno.TypeType" // }, // "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IncCounter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "File": "tests.gno", -// "Line": "11", -// "Nonce": "0", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, +// "@type": "/gno.TypeValue", // "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.PrimitiveType", +// "value": "2048" +// }, +// "Methods": [], +// "Name": "Word", +// "PkgPath": "gno.land/r/demo/tests" // } // } // }, // { // "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ] +// "@type": "/gno.TypeType" // }, // "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "Counter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "File": "tests.gno", -// "Line": "15", -// "Nonce": "0", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, +// "@type": "/gno.TypeValue", // "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.SliceType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.Word" +// }, +// "Vrd": false +// }, +// "Methods": [ // { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, +// "T": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "n", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": null, +// "FileName": "realm_method38d.gno", +// "IsMethod": true, +// "Name": "Add", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "realm_method38d.gno", +// "Line": "5", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "n", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ] +// } +// } +// } +// ], +// "Name": "nat", +// "PkgPath": "gno.land/r/demo/tests" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.Int" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "49283398258e135138cd8e234142d5daaa8c661d", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "neg", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// }, +// { +// "Embedded": false, +// "Name": "abs", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ], +// "PkgPath": "gno.land/r/demo/tests" +// }, +// "Methods": [], +// "Name": "Int", +// "PkgPath": "gno.land/r/demo/tests" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" // } // } // ] @@ -564,11 +535,11 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:7" // }, -// "FileName": "tests.gno", +// "FileName": "realm_compositelit.gno", // "IsMethod": false, -// "Name": "CurrentRealmPath", +// "Name": "GetZeroType", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", @@ -576,9 +547,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { -// "File": "tests.gno", +// "Column": "1", +// "File": "realm_compositelit.gno", // "Line": "19", -// "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -591,8 +562,70 @@ func main() { // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" +// }, +// "FileName": "realm_method38d.gno", +// "IsMethod": false, +// "Name": "GetAbs", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "realm_method38d.gno", +// "Line": "9", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.nat" // } // } // ] @@ -610,7 +643,7 @@ func main() { // "Tag": "", // "Type": { // "@type": "/gno.RefType", -// "ID": "std.Address" +// "ID": "gno.land/r/demo/tests.nat" // } // } // ] @@ -620,11 +653,11 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" // }, -// "FileName": "tests.gno", +// "FileName": "realm_method38d.gno", // "IsMethod": false, -// "Name": "InitOrigCaller", +// "Name": "AbsAdd", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", @@ -632,9 +665,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { -// "File": "tests.gno", -// "Line": "25", -// "Nonce": "0", +// "Column": "1", +// "File": "realm_method38d.gno", +// "Line": "15", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -648,7 +681,7 @@ func main() { // "Tag": "", // "Type": { // "@type": "/gno.RefType", -// "ID": "std.Address" +// "ID": "gno.land/r/demo/tests.nat" // } // } // ] @@ -657,6 +690,12 @@ func main() { // }, // { // "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { // "@type": "/gno.FuncType", // "Params": [], // "Results": [] @@ -666,11 +705,11 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, -// "Name": "AssertOriginCall", +// "Name": "IncCounter", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", @@ -678,9 +717,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "29", -// "Nonce": "0", +// "Line": "12", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -702,7 +741,7 @@ func main() { // "Tag": "", // "Type": { // "@type": "/gno.PrimitiveType", -// "value": "4" +// "value": "32" // } // } // ] @@ -712,11 +751,11 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, -// "Name": "IsOriginCall", +// "Name": "Counter", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", @@ -724,9 +763,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "33", -// "Nonce": "0", +// "Line": "16", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -740,7 +779,7 @@ func main() { // "Tag": "", // "Type": { // "@type": "/gno.PrimitiveType", -// "value": "4" +// "value": "32" // } // } // ] @@ -749,63 +788,525 @@ func main() { // }, // { // "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "ModifyTestRealmObject", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "File": "tests.gno", -// "Line": "44", -// "Nonce": "0", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "CurrentRealmPath", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "20", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "InitOrigCaller", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "26", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "CallAssertOriginCall", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "30", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "CallIsOriginCall", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "34", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "CallSubtestsAssertOriginCall", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "38", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "CallSubtestsIsOriginCall", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "42", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "Field", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ], +// "PkgPath": "gno.land/r/demo/tests" +// }, +// "Methods": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "t", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestRealmObject" +// } +// } +// } +// ], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": null, +// "FileName": "tests.gno", +// "IsMethod": true, +// "Name": "Modify", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "57", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "t", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestRealmObject" +// } +// } +// } +// ], +// "Results": [] +// } +// } +// } +// ], +// "Name": "TestRealmObject", +// "PkgPath": "gno.land/r/demo/tests" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "t", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestRealmObject" +// } +// } +// } +// ], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "ModifyTestRealmObject", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "53", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "t", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestRealmObject" +// } +// } +// } +// ], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "Name", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// }, +// { +// "Embedded": false, +// "Name": "Child", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestNode" +// } +// } +// } +// ], +// "PkgPath": "gno.land/r/demo/tests" +// }, +// "Methods": [], +// "Name": "TestNode", +// "PkgPath": "gno.land/r/demo/tests" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestNode" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestNode" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests.TestNode" // } // } // }, @@ -820,7 +1321,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, @@ -832,9 +1333,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "66", -// "Nonce": "0", +// "Line": "75", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -856,7 +1357,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, @@ -868,9 +1369,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "71", -// "Nonce": "0", +// "Line": "80", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -892,7 +1393,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, @@ -904,9 +1405,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "79", -// "Nonce": "0", +// "Line": "88", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -938,7 +1439,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, @@ -950,9 +1451,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "83", -// "Nonce": "0", +// "Line": "92", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -994,7 +1495,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, @@ -1006,9 +1507,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "87", -// "Nonce": "0", +// "Line": "96", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1051,7 +1552,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, // "FileName": "tests.gno", // "IsMethod": false, @@ -1063,9 +1564,9 @@ func main() { // "@type": "/gno.RefNode", // "BlockNode": null, // "Location": { +// "Column": "1", // "File": "tests.gno", -// "Line": "91", -// "Nonce": "0", +// "Line": "100", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1089,71 +1590,175 @@ func main() { // }, // { // "T": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// }, -// "Vrd": false +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] // }, // "V": { -// "@type": "/gno.SliceValue", -// "Base": { +// "@type": "/gno.FuncValue", +// "Closure": { // "@type": "/gno.RefValue", -// "Hash": "5e5535af7afef6f523a897c051944639ef56c057", +// "Escaped": true, // "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" // }, -// "Length": "3", -// "Maxcap": "3", -// "Offset": "0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "IsCallerSubPath", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "104", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] +// } // } // }, // { // "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] // }, // "V": { -// "@type": "/gno.StringValue", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "IsCallerParentPath", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "108", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] // } // } // }, // { // "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "HasCallerSameNamespace", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "Column": "1", +// "File": "tests.gno", +// "Line": "112", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "4" +// } +// } +// ] // } // } // } // ] // } -// d[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:7] +// d[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13] // switchrealm["gno.land/r/demo/tests_foo"] // switchrealm["gno.land/r/demo/tests_foo"] // switchrealm["gno.land/r/demo/tests_foo"] diff --git a/gnovm/tests/files/zrealm_testutils0.gno b/gnovm/tests/files/zrealm_testutils0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_testutils0.gno rename to gnovm/tests/files/zrealm_testutils0_stdlibs.gno diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index d5541fb0554..f18ac1093c5 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -23,13 +23,12 @@ import ( "log" "math" "math/big" - "math/rand" + "math/rand/v2" "net" "net/url" "os" "path/filepath" "reflect" - "sort" "strconv" "strings" "sync" @@ -39,8 +38,8 @@ import ( "unicode/utf8" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" + teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/db/memdb" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" @@ -62,8 +61,8 @@ const ( ) // NOTE: this isn't safe, should only be used for testing. -func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { +func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { + 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")) } @@ -80,16 +79,17 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri baseDir := filepath.Join(filesPath, "extern", pkgPath[len(testPath):]) memPkg := gno.ReadMemPackage(baseDir, pkgPath) send := std.Coins{} - ctx := testContext(pkgPath, send) + ctx := TestContext(pkgPath, send) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, Context: ctx, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() // m2.SetActivePackage(pv) + // XXX remove second arg 'false' and remove all gonative stuff. return m2.RunMemPackage(memPkg, false) } } @@ -97,7 +97,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -115,7 +115,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || pkgPath == "math/big" || - pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || mode == ImportModeNativePreferred { switch pkgPath { @@ -187,7 +186,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri d := arg0.GetInt64() sec := d / int64(time.Second) nano := d % int64(time.Second) - ctx := m.Context.(stdlibs.ExecContext) + ctx := m.Context.(*teststd.TestExecContext) ctx.Timestamp += sec ctx.TimestampNano += nano if ctx.TimestampNano >= int64(time.Second) { @@ -258,26 +257,24 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeValue("Pi", math.Pi) pkg.DefineGoNativeValue("MaxFloat32", math.MaxFloat32) pkg.DefineGoNativeValue("MaxFloat64", math.MaxFloat64) - pkg.DefineGoNativeValue("MaxUint32", math.MaxUint32) + pkg.DefineGoNativeValue("MaxUint32", uint32(math.MaxUint32)) pkg.DefineGoNativeValue("MaxUint64", uint64(math.MaxUint64)) pkg.DefineGoNativeValue("MinInt8", math.MinInt8) pkg.DefineGoNativeValue("MinInt16", math.MinInt16) pkg.DefineGoNativeValue("MinInt32", math.MinInt32) - pkg.DefineGoNativeValue("MinInt64", math.MinInt64) + pkg.DefineGoNativeValue("MinInt64", int64(math.MinInt64)) pkg.DefineGoNativeValue("MaxInt8", math.MaxInt8) pkg.DefineGoNativeValue("MaxInt16", math.MaxInt16) pkg.DefineGoNativeValue("MaxInt32", math.MaxInt32) - pkg.DefineGoNativeValue("MaxInt64", math.MaxInt64) + pkg.DefineGoNativeValue("MaxInt64", int64(math.MaxInt64)) return pkg, pkg.NewPackage() case "math/rand": // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) - pkg.DefineGoNativeValue("Intn", rand.Intn) - pkg.DefineGoNativeValue("Uint32", rand.Uint32) - pkg.DefineGoNativeValue("Seed", rand.Seed) - pkg.DefineGoNativeValue("New", rand.New) - pkg.DefineGoNativeValue("NewSource", rand.NewSource) - pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) + // make native rand same as gno rand. + rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec + pkg.DefineGoNativeValue("IntN", rnd.IntN) + pkg.DefineGoNativeValue("Uint32", rnd.Uint32) return pkg, pkg.NewPackage() case "crypto/rand": pkg := gno.NewPackageNode("rand", pkgPath, nil) @@ -332,11 +329,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg := gno.NewPackageNode("big", pkgPath, nil) pkg.DefineGoNativeValue("NewInt", big.NewInt) return pkg, pkg.NewPackage() - case "sort": - pkg := gno.NewPackageNode("sort", pkgPath, nil) - pkg.DefineGoNativeValue("Strings", sort.Strings) - // pkg.DefineGoNativeValue("Sort", sort.Sort) - return pkg, pkg.NewPackage() case "flag": pkg := gno.NewPackageNode("flag", pkgPath, nil) pkg.DefineGoNativeType(reflect.TypeOf(flag.Flag{})) @@ -381,7 +373,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -396,11 +388,11 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri } send := std.Coins{} - ctx := testContext(pkgPath, send) + ctx := TestContext(pkgPath, send) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, Context: ctx, }) pn, pv = m2.RunMemPackage(memPkg, true) @@ -408,15 +400,15 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri } return nil, nil } - // NOTE: store is also used in closure above. db := memdb.NewMemDB() baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) - store = gno.NewStore(nil, baseStore, iavlStore) - store.SetPackageGetter(getPackage) - store.SetNativeStore(teststdlibs.NativeStore) - store.SetPackageInjector(testPackageInjector) - store.SetStrictGo2GnoMapping(false) + // make a new store + resStore = gno.NewStore(nil, baseStore, iavlStore) + resStore.SetPackageGetter(getPackage) + resStore.SetNativeStore(teststdlibs.NativeStore) + resStore.SetPackageInjector(testPackageInjector) + resStore.SetStrictGo2GnoMapping(false) return } @@ -473,7 +465,7 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) { } } -//---------------------------------------- +// ---------------------------------------- type dummyReader struct{} @@ -484,7 +476,7 @@ func (*dummyReader) Read(b []byte) (n int, err error) { return len(b), nil } -//---------------------------------------- +// ---------------------------------------- type TestReport struct { Name string diff --git a/gnovm/tests/integ/debugger/sample.gno b/gnovm/tests/integ/debugger/sample.gno index ecd980acf05..354b00b547e 100644 --- a/gnovm/tests/integ/debugger/sample.gno +++ b/gnovm/tests/integ/debugger/sample.gno @@ -38,5 +38,9 @@ func main() { } t := T{A: []int{1, 2, 3} } println(t.get(1)) - println("bye") + x := 0 + for i := 0; i < 5; i++ { + x = i + } + println("bye", x) } diff --git a/gnovm/tests/integ/invalid_assign/main.gno b/gnovm/tests/integ/invalid_assign/main.gno new file mode 100644 index 00000000000..9f98ef31213 --- /dev/null +++ b/gnovm/tests/integ/invalid_assign/main.gno @@ -0,0 +1,16 @@ +// This should result in a compilation error for assigning a bool to a custom bool type without conversion + +package main + +func main() { + b := true + var v C = b + fmt.Println(v) +} + +type C bool + +const ( + F C = false + T C = true +) diff --git a/gnovm/tests/integ/several-lint-errors/gno.mod b/gnovm/tests/integ/several-lint-errors/gno.mod new file mode 100644 index 00000000000..88485411822 --- /dev/null +++ b/gnovm/tests/integ/several-lint-errors/gno.mod @@ -0,0 +1 @@ +module gno.land/tests/severalerrors \ No newline at end of file diff --git a/gnovm/tests/integ/several-lint-errors/main.gno b/gnovm/tests/integ/several-lint-errors/main.gno new file mode 100644 index 00000000000..f29aa7ecd33 --- /dev/null +++ b/gnovm/tests/integ/several-lint-errors/main.gno @@ -0,0 +1,6 @@ +package main + +func main() { + for { + _ example +} \ No newline at end of file diff --git a/gnovm/tests/integ/unformated/missing_import.gno b/gnovm/tests/integ/unformated/missing_import.gno new file mode 100644 index 00000000000..03caed3433e --- /dev/null +++ b/gnovm/tests/integ/unformated/missing_import.gno @@ -0,0 +1,3 @@ +package gno + +var hello = strconv.Itoa(42) diff --git a/gnovm/tests/package_test.go b/gnovm/tests/package_test.go index a3f9e587030..8e497941c7f 100644 --- a/gnovm/tests/package_test.go +++ b/gnovm/tests/package_test.go @@ -7,7 +7,6 @@ import ( "log" "os" "path/filepath" - "sort" "strings" "testing" @@ -16,11 +15,14 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" ) -func TestPackages(t *testing.T) { +func TestStdlibs(t *testing.T) { + // NOTE: this test only works using _test.gno files; + // filetests are not meant to be used for testing standard libraries. + // The examples directory is tested directly using `gno test`u + // find all packages with *_test.gno files. rootDirs := []string{ filepath.Join("..", "stdlibs"), - filepath.Join("..", "..", "examples"), } testDirs := map[string]string{} // aggregate here, pkgPath -> dir pkgPaths := []string{} @@ -45,114 +47,10 @@ func TestPackages(t *testing.T) { return nil }) } - // Sort pkgPaths for determinism. - sort.Strings(pkgPaths) // For each package with testfiles (in testDirs), call Machine.TestMemPackage. for _, pkgPath := range pkgPaths { testDir := testDirs[pkgPath] t.Run(pkgPath, func(t *testing.T) { - t.Skip("almost any new package is failing. Ignoring this test for now until we find a solution for this.") - - if pkgPath == "gno.land/p/demo/avl" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/flow" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/grc/exts/vault" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/grc/grc1155" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/grc/grc20" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/grc/grc721" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/memeland" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/ownable" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/pausable" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/rand" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/tests" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/p/demo/todolist" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/art/gnoface" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/foo1155" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/foo20" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/keystore" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/microblog" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/tests" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/todolist" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/demo/userbook" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/gnoland/blog" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/gnoland/faucet" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/x/manfred_outfmt" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/x/nir1218_evaluation_proposal" { - t.Skip("package failing") - } - - if pkgPath == "gno.land/r/gnoland/ghverify" { - t.Skip("package failing") - } - runPackageTest(t, testDir, pkgPath) }) } diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/generated.go similarity index 75% rename from gnovm/tests/stdlibs/native.go rename to gnovm/tests/stdlibs/generated.go index 81100838784..23bb3cfa594 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/generated.go @@ -11,20 +11,31 @@ import ( testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" ) -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ { "std", "AssertOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { testlibs_std.AssertOriginCall( m, @@ -38,6 +49,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + true, func(m *gno.Machine) { r0 := testlibs_std.IsOriginCall( m, @@ -50,25 +62,6 @@ var nativeFuncs = [...]nativeFunc{ )) }, }, - { - "std", - "TestCurrentRealm", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - }, - func(m *gno.Machine) { - r0 := testlibs_std.TestCurrentRealm( - m, - ) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, { "std", "TestSkipHeights", @@ -76,6 +69,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -95,6 +89,7 @@ var nativeFuncs = [...]nativeFunc{ "ClearStoreCache", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { testlibs_std.ClearStoreCache( m, @@ -110,6 +105,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -137,6 +133,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -158,6 +155,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -174,44 +172,28 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "testSetPrevRealm", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - }, - []gno.FieldTypeExpr{}, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - - testlibs_std.X_testSetPrevRealm( - m, - p0) - }, - }, - { - "std", - "testSetPrevAddr", + "testSetRealm", []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() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - testlibs_std.X_testSetPrevAddr( + testlibs_std.X_testSetRealm( m, - p0) + p0, p1) }, }, { @@ -224,6 +206,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -256,6 +239,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p2"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -276,6 +260,42 @@ var nativeFuncs = [...]nativeFunc{ p0, p1, p2) }, }, + { + "std", + "getRealm", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("string")}, + }, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0, r1 := testlibs_std.X_getRealm( + m, + 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(), + )) + }, + }, { "testing", "unixNano", @@ -283,6 +303,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + false, func(m *gno.Machine) { r0 := testlibs_testing.X_unixNano() @@ -294,3 +315,19 @@ var nativeFuncs = [...]nativeFunc{ }, }, } + +var initOrder = [...]string{ + "std", + "testing", +} + +// InitOrder returns the initialization order of the standard libraries. +// This is calculated starting from the list of all standard libraries and +// iterating through each: if a package depends on an unitialized package, that +// is processed first, and so on recursively; matching the behaviour of Go's +// [program initialization]. +// +// [program initialization]: https://go.dev/ref/spec#Program_initialization +func InitOrder() []string { + return initOrder[:] +} diff --git a/gnovm/tests/stdlibs/std/frame_testing.gno b/gnovm/tests/stdlibs/std/frame_testing.gno new file mode 100644 index 00000000000..171756c54f5 --- /dev/null +++ b/gnovm/tests/stdlibs/std/frame_testing.gno @@ -0,0 +1,12 @@ +package std + +// NewUserRealm creates a new "user" realm with the given address. +func NewUserRealm(user Address) Realm { + return Realm{addr: user} +} + +// NewCodeRealm creates a new realm for a published code realm with the given +// pkgPath. +func NewCodeRealm(pkgPath string) Realm { + return Realm{pkgPath: pkgPath, addr: DerivePkgAddr(pkgPath)} +} diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 0a4f9cc6eff..f583342ae04 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -2,19 +2,25 @@ package std func AssertOriginCall() // injected func IsOriginCall() bool // injected -func TestCurrentRealm() string // injected func TestSkipHeights(count int64) // injected func ClearStoreCache() // injected func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } -func TestSetPrevRealm(pkgPath string) { testSetPrevRealm(pkgPath) } -func TestSetPrevAddr(addr Address) { testSetPrevAddr(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. +func TestSetRealm(rlm Realm) { + testSetRealm(string(rlm.addr), rlm.pkgPath) +} + func TestSetOrigSend(sent, spent Coins) { sentDenom, sentAmt := sent.expandNative() spentDenom, spentAmt := spent.expandNative() testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) } + func TestIssueCoins(addr Address, coins Coins) { denom, amt := coins.expandNative() testIssueCoins(string(addr), denom, amt) @@ -26,9 +32,9 @@ func callerAt(n int) string // native bindings func testSetOrigCaller(s string) func testSetOrigPkgAddr(s string) -func testSetPrevRealm(s string) -func testSetPrevAddr(s string) +func testSetRealm(addr, pkgPath string) func testSetOrigSend( sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64) func testIssueCoins(addr string, denom []string, amt []int64) +func getRealm(height int) (address string, pkgPath string) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 72a2a7734ed..0421f359932 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -5,13 +5,26 @@ import ( "strings" "testing" - "github.com/gnolang/gno/gnovm/stdlibs" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" ) +// TestExecContext is the testing extension of the exec context. +type TestExecContext struct { + std.ExecContext + + // These are used to set up the result of CurrentRealm() and PrevRealm(). + RealmFrames map[*gno.Frame]RealmOverride +} + +var _ std.ExecContexter = &TestExecContext{} + +type RealmOverride struct { + Addr crypto.Bech32Address + PkgPath string +} + func AssertOriginCall(m *gno.Machine) { if !IsOriginCall(m) { m.Panic(typedString("invalid non-origin call")) @@ -41,13 +54,10 @@ func IsOriginCall(m *gno.Machine) bool { panic("unable to determine if test is a _test or a _filetest") } -func TestCurrentRealm(m *gno.Machine) string { - return m.Realm.Path -} - func TestSkipHeights(m *gno.Machine, count int64) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.Height += count + ctx.Timestamp += (count * 5) m.Context = ctx } @@ -84,44 +94,90 @@ func X_callerAt(m *gno.Machine, n int) string { } if n == m.NumFrames()-1 { // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) return string(ctx.OrigCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } func X_testSetOrigCaller(m *gno.Machine, addr string) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.OrigPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } -func X_testSetPrevRealm(m *gno.Machine, pkgPath string) { - m.Frames[m.NumFrames()-2].LastPackage = &gno.PackageValue{PkgPath: pkgPath} +func X_testSetRealm(m *gno.Machine, addr, pkgPath string) { + // Associate the given Realm with the caller's frame. + var frame *gno.Frame + // When calling this function from Gno, the two top frames are the following: + // #6 [FRAME FUNC:testSetRealm RECV:(undefined) (2 args) 17/6/0/10/8 LASTPKG:std ...] + // #5 [FRAME FUNC:TestSetRealm RECV:(undefined) (1 args) 14/5/0/8/7 LASTPKG:gno.land/r/tyZ1Vcsta ...] + // We want to set the Realm of the frame where TestSetRealm is being called, hence -3. + for i := m.NumFrames() - 3; i >= 0; i-- { + // Must be a frame from calling a function. + if fr := m.Frames[i]; fr.Func != nil { + frame = fr + break + } + } + + ctx := m.Context.(*TestExecContext) + ctx.RealmFrames[frame] = RealmOverride{ + Addr: crypto.Bech32Address(addr), + PkgPath: pkgPath, + } } -func X_testSetPrevAddr(m *gno.Machine, addr string) { - // clear all frames to return mocked origin caller - for i := m.NumFrames() - 1; i > 0; i-- { - m.Frames[i].LastPackage = nil +func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { + // NOTE: keep in sync with stdlibs/std.getRealm + + var ( + ctx = m.Context.(*TestExecContext) + currentCaller crypto.Bech32Address + // Keeps track of the number of times currentCaller + // has changed. + changes int + ) + + for i := m.NumFrames() - 1; i >= 0; i-- { + fr := m.Frames[i] + override, overridden := ctx.RealmFrames[m.Frames[max(i-1, 0)]] + if !overridden && + (fr.LastPackage == nil || !fr.LastPackage.IsRealm()) { + continue + } + + // LastPackage is a realm. Get caller and pkgPath, and compare against + // currentCaller. + caller, pkgPath := override.Addr, override.PkgPath + if !overridden { + caller = fr.LastPackage.GetPkgAddr().Bech32() + pkgPath = fr.LastPackage.PkgPath + } + if caller != currentCaller { + if changes == height { + return string(caller), pkgPath + } + currentCaller = caller + changes++ + } } - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) - m.Context = ctx + // Fallback case: return OrigCaller. + return string(ctx.OrigCaller), "" } func X_testSetOrigSend(m *gno.Machine, sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64, ) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent @@ -129,7 +185,7 @@ func X_testSetOrigSend(m *gno.Machine, } func X_testIssueCoins(m *gno.Machine, addr string, denom []string, amt []int64) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) banker := ctx.Banker for i := range denom { banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i]) diff --git a/go.mod b/go.mod index b5ef5021fac..c155a23f16b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/gnolang/gno -go 1.21 +go 1.22 + +toolchain go1.22.4 require ( dario.cat/mergo v1.0.0 @@ -24,49 +26,52 @@ require ( 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/rs/cors v1.10.1 + github.com/rs/cors v1.11.0 github.com/rs/xid v1.5.0 github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 go.etcd.io/bbolt v1.3.9 - go.opentelemetry.io/otel v1.25.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 - go.opentelemetry.io/otel/metric v1.25.0 - go.opentelemetry.io/otel/sdk v1.25.0 - go.opentelemetry.io/otel/sdk/metric v1.25.0 + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 + go.opentelemetry.io/otel/metric v1.28.0 + go.opentelemetry.io/otel/sdk v1.28.0 + go.opentelemetry.io/otel/sdk/metric v1.28.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.2.0 - golang.org/x/crypto v0.21.0 - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/mod v0.16.0 - golang.org/x/net v0.23.0 - golang.org/x/term v0.18.0 - golang.org/x/tools v0.19.0 - google.golang.org/protobuf v1.33.0 + golang.org/x/crypto v0.25.0 + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 + golang.org/x/mod v0.19.0 + golang.org/x/net v0.27.0 + golang.org/x/sync v0.7.0 + golang.org/x/term v0.22.0 + golang.org/x/tools v0.22.0 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect ) diff --git a/go.sum b/go.sum index e4d728a106d..9fde89e8cec 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7 github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -78,6 +78,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -91,8 +93,8 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/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= @@ -136,8 +138,8 @@ github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/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/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -153,20 +155,22 @@ 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.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -179,14 +183,14 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/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= @@ -196,14 +200,14 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.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= @@ -220,47 +224,46 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/autocounterd/README.md b/misc/autocounterd/README.md index 28b44312f25..b844a3d9e6c 100644 --- a/misc/autocounterd/README.md +++ b/misc/autocounterd/README.md @@ -2,7 +2,7 @@ ## What is it? -`autcounterd` is a simple binary that increments a simple counter every x seconds to analyse and test a network. +`autocounterd` is a simple binary that increments a simple counter every x seconds to analyse and test a network. ## How to use diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index d9d26eab37a..12297e3c6ca 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,6 +1,8 @@ module loop -go 1.21 +go 1.22 + +toolchain go1.22.4 require github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278 diff --git a/misc/deployments/staging.gno.land/docker-compose.yml b/misc/deployments/staging.gno.land/docker-compose.yml index 991a102b691..3479067372d 100644 --- a/misc/deployments/staging.gno.land/docker-compose.yml +++ b/misc/deployments/staging.gno.land/docker-compose.yml @@ -6,7 +6,7 @@ services: build: ../../.. environment: - VIRTUAL_HOST=rpc.staging.gno.land - - VIRTUAL_PORT=36657 + - VIRTUAL_PORT=26657 - LETSENCRYPT_HOST=rpc.staging.gno.land - LOG_LEVEL=4 working_dir: /opt/gno/src/gno.land @@ -15,12 +15,12 @@ services: - start - --skip-failing-genesis-txs - --chainid=staging - - --genesis-remote=staging.gno.land:36657 + - --genesis-remote=staging.gno.land:26657 volumes: - "./data/gnoland:/opt/gno/src/gno.land/gnoland-data" ports: - - 36656:36656 - - 36657:36657 + - 26656:26656 + - 26657:26657 restart: on-failure logging: driver: "json-file" @@ -34,11 +34,11 @@ services: command: - gnoweb - --bind=0.0.0.0:80 - - --remote=gnoland:36657 + - --remote=gnoland:26657 - --captcha-site=$RECAPTCHA_SITE_KEY - --faucet-url=https://faucet-staging.gno.land/ - --help-chainid=staging - - --help-remote=staging.gno.land:36657 + - --help-remote=staging.gno.land:26657 - --with-analytics volumes: - "./overlay:/overlay:ro" @@ -64,7 +64,7 @@ services: mkdir -p /.gno && expect -c \"set timeout -1; spawn gnokey add --home /.gno/ --recover faucet; expect \\\"Enter a passphrase\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect \\\"Repeat the passphrase\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect \\\"Enter your bip39 mnemonic\\\"; send \\\"$FAUCET_WORDS\\r\\\"; expect eof\" && while true; do - expect -c \"set timeout -1; spawn gnofaucet serve --send 50000000ugnot --captcha-secret \\\"$RECAPTCHA_SECRET_KEY\\\" --remote gnoland:36657 --chain-id staging --home /.gno/ faucet; expect \\\"Enter password\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; + expect -c \"set timeout -1; spawn gnofaucet serve --send 50000000ugnot --captcha-secret \\\"$RECAPTCHA_SECRET_KEY\\\" --remote gnoland:26657 --chain-id staging --home /.gno/ faucet; expect \\\"Enter password\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; sleep 5; done " diff --git a/misc/deployments/staging.gno.land/overlay/config.toml b/misc/deployments/staging.gno.land/overlay/config.toml index 1f5952f9638..89746e75c4f 100644 --- a/misc/deployments/staging.gno.land/overlay/config.toml +++ b/misc/deployments/staging.gno.land/overlay/config.toml @@ -72,7 +72,7 @@ filter_peers = false [rpc] # TCP or UNIX socket address for the RPC server to listen on -laddr = "tcp://0.0.0.0:36657" +laddr = "tcp://0.0.0.0:26657" # A list of origins a cross-domain request can be executed from # Default value '[]' disables cors support @@ -139,7 +139,7 @@ tls_key_file = "" [p2p] # Address to listen for incoming connections -laddr = "tcp://0.0.0.0:36656" +laddr = "tcp://0.0.0.0:26656" # Address to advertise to peers for them to dial # If empty, will use the same port as the laddr, diff --git a/misc/deployments/test2.gno.land/Makefile b/misc/deployments/test2.gno.land/Makefile index ad334f31243..a77a81f62d0 100644 --- a/misc/deployments/test2.gno.land/Makefile +++ b/misc/deployments/test2.gno.land/Makefile @@ -25,9 +25,9 @@ build: gnotxport: #docker build -t gno ../.. - docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport export --remote test2.gno.land:36657 + docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport export --remote test2.gno.land:26657 tail -n 1 txexport.log wc -l txexport.log gnotximport: - docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport import --remote test2.gno.land:36657 + docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport import --remote test2.gno.land:26657 diff --git a/misc/deployments/test2.gno.land/docker-compose.yml b/misc/deployments/test2.gno.land/docker-compose.yml index c836c0cc65f..ad613b3f246 100644 --- a/misc/deployments/test2.gno.land/docker-compose.yml +++ b/misc/deployments/test2.gno.land/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: - LOG_LEVEL=4 - VIRTUAL_HOST=rpc.test2.gno.land - - VIRTUAL_PORT=36657 + - VIRTUAL_PORT=26657 - LETSENCRYPT_HOST=rpc.test2.gno.land command: - gnoland @@ -16,15 +16,15 @@ services: - --chainid - test2 - --genesis-remote - - test2.gno.land:36657 + - test2.gno.land:26657 - --genesis-balances-file - /overlay/balances.txt volumes: - "./data/gnoland:/opt/gno/src/testdir" - "./overlay:/overlay:ro" ports: - - 36656:36656 - - 36657:36657 + - 26656:26656 + - 26657:26657 restart: on-failure logging: driver: "json-file" @@ -40,7 +40,7 @@ services: - --bind - 0.0.0.0:80 - --remote - - gnoland:36657 + - gnoland:26657 - --captcha-site - $RECAPTCHA_SITE_KEY - --faucet-url @@ -48,7 +48,7 @@ services: - --help-chainid - test2 - --help-remote - - test2.gno.land:36657 + - test2.gno.land:26657 - --home-content - /overlay/HOME.md volumes: @@ -75,7 +75,7 @@ services: mkdir -p /.gno && expect -c \"set timeout -1; spawn gnokey add faucet --home /.gno/ --recover; expect \\\"Enter a passphrase\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect \\\"Repeat the passphrase\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect \\\"Enter your bip39 mnemonic\\\"; send \\\"$FAUCET_WORDS\\r\\\"; expect eof\" && while true; do - expect -c \"set timeout -1; spawn gnofaucet serve faucet --is-behind-proxy --send 50000000ugnot --captcha-secret \\\"$RECAPTCHA_SECRET_KEY\\\" --remote gnoland:36657 --chain-id test2 --home /.gno/; expect \\\"Enter password.\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; + expect -c \"set timeout -1; spawn gnofaucet serve faucet --is-behind-proxy --send 50000000ugnot --captcha-secret \\\"$RECAPTCHA_SECRET_KEY\\\" --remote gnoland:26657 --chain-id test2 --home /.gno/; expect \\\"Enter password.\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; sleep 5; done " diff --git a/misc/deployments/test2.gno.land/overlay/config.toml b/misc/deployments/test2.gno.land/overlay/config.toml index 65e34a2c55c..ac34760de62 100644 --- a/misc/deployments/test2.gno.land/overlay/config.toml +++ b/misc/deployments/test2.gno.land/overlay/config.toml @@ -72,7 +72,7 @@ filter_peers = false [rpc] # TCP or UNIX socket address for the RPC server to listen on -laddr = "tcp://0.0.0.0:36657" +laddr = "tcp://0.0.0.0:26657" # A list of origins a cross-domain request can be executed from # Default value '[]' disables cors support @@ -139,7 +139,7 @@ tls_key_file = "" [p2p] # Address to listen for incoming connections -laddr = "tcp://0.0.0.0:36656" +laddr = "tcp://0.0.0.0:26656" # Address to advertise to peers for them to dial # If empty, will use the same port as the laddr, diff --git a/misc/deployments/test3.gno.land/Makefile b/misc/deployments/test3.gno.land/Makefile index d52b95b260b..5d998e8cc6f 100644 --- a/misc/deployments/test3.gno.land/Makefile +++ b/misc/deployments/test3.gno.land/Makefile @@ -28,9 +28,9 @@ build: gnotxport: #docker build -t gno ../.. - docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport export --remote test3.gno.land:36657 + docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport export --remote test3.gno.land:26657 tail -n 1 txexport.log wc -l txexport.log gnotximport: - docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport import --remote test3.gno.land:36657 + docker run -v "$(PWD):$(PWD)" -w "$(PWD)" gno gnotxport import --remote test3.gno.land:26657 diff --git a/misc/deployments/test3.gno.land/docker-compose.yml b/misc/deployments/test3.gno.land/docker-compose.yml index b76d5c3e379..62adda7cc3e 100644 --- a/misc/deployments/test3.gno.land/docker-compose.yml +++ b/misc/deployments/test3.gno.land/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: - LOG_LEVEL=4 - VIRTUAL_HOST=rpc.test3.gno.land - - VIRTUAL_PORT=36657 + - VIRTUAL_PORT=26657 - LETSENCRYPT_HOST=rpc.test3.gno.land command: - gnoland @@ -16,7 +16,7 @@ services: - --chainid - test3 - --genesis-remote - - test3.gno.land:36657 + - test3.gno.land:26657 - --genesis-balances-file - /overlay/balances.txt - --genesis-txs-file @@ -25,8 +25,9 @@ services: - "./data/gnoland:/opt/gno/src/testdir" - "./overlay:/overlay:ro" ports: - - 36656:36656 - - 36657:36657 + - 26656:26656 + - 26657:26657 + - 36657:26657 restart: on-failure logging: driver: "json-file" @@ -42,7 +43,7 @@ services: - --bind - 0.0.0.0:80 - --remote - - gnoland:36657 + - gnoland:26657 - --captcha-site - $RECAPTCHA_SITE_KEY - --faucet-url @@ -50,7 +51,7 @@ services: - --help-chainid - test3 - --help-remote - - test3.gno.land:36657 + - test3.gno.land:26657 - --home-content - /overlay/HOME.md volumes: @@ -77,7 +78,7 @@ services: mkdir -p /.gno && expect -c \"set timeout -1; spawn gnokey add faucet --home /.gno/ --recover; expect \\\"Enter a passphrase\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect \\\"Repeat the passphrase\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect \\\"Enter your bip39 mnemonic\\\"; send \\\"$FAUCET_WORDS\\r\\\"; expect eof\" && while true; do - expect -c \"set timeout -1; spawn gnofaucet serve faucet --is-behind-proxy --send 50000000ugnot --captcha-secret \\\"$RECAPTCHA_SECRET_KEY\\\" --remote gnoland:36657 --chain-id test3 --home /.gno/; expect \\\"Enter password.\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; + expect -c \"set timeout -1; spawn gnofaucet serve faucet --is-behind-proxy --send 50000000ugnot --captcha-secret \\\"$RECAPTCHA_SECRET_KEY\\\" --remote gnoland:26657 --chain-id test3 --home /.gno/; expect \\\"Enter password.\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; sleep 5; done " diff --git a/misc/deployments/test3.gno.land/overlay/config.toml b/misc/deployments/test3.gno.land/overlay/config.toml index 65e34a2c55c..ac34760de62 100644 --- a/misc/deployments/test3.gno.land/overlay/config.toml +++ b/misc/deployments/test3.gno.land/overlay/config.toml @@ -72,7 +72,7 @@ filter_peers = false [rpc] # TCP or UNIX socket address for the RPC server to listen on -laddr = "tcp://0.0.0.0:36657" +laddr = "tcp://0.0.0.0:26657" # A list of origins a cross-domain request can be executed from # Default value '[]' disables cors support @@ -139,7 +139,7 @@ tls_key_file = "" [p2p] # Address to listen for incoming connections -laddr = "tcp://0.0.0.0:36656" +laddr = "tcp://0.0.0.0:26656" # Address to advertise to peers for them to dial # If empty, will use the same port as the laddr, diff --git a/misc/deployments/test4.gno.land/README.md b/misc/deployments/test4.gno.land/README.md new file mode 100644 index 00000000000..6277ea996ec --- /dev/null +++ b/misc/deployments/test4.gno.land/README.md @@ -0,0 +1,40 @@ +## Overview + +This deployment folder contains minimal information needed to launch a full test4.gno.land validator node. + +## `genesis.json` + +The initial `genesis.json` validator set is consisted of 3 entities (7 validators in total): + +- Gno Core - the gno core team (**4 validators**) +- Gno DevX - the gno devX team (**2 validators**) +- Onbloc - the [Onbloc](https://onbloc.xyz/) team (**1 validator**) + +Subsequent validators will be added through the governance mechanism in govdao, employing a preliminary simplified +version Proof of Contribution. + +The addresses premined belong to different faucet accounts, validator entities and implementation partners. + +## `config.toml` + +The `config.toml` located in this directory is a **_guideline_**, and not a definitive configuration on how +all nodes should be configured in the network. + +### Important params + +Some configuration params are required, while others are advised to be set. + +- `moniker` - the recognizable identifier of the node. +- `consensus.timeout_commit` - the timeout value after the consensus commit phase. ⚠️ **Required to be `2s`** ⚠️. +- `mempool.size` - the maximum number of txs in the mempool. **Advised to be `10000`**. +- `p2p.laddr` - the listen address for P2P traffic, **specific to every node deployment**. It is advised to use a + reverse-proxy, and keep this value at `tcp://0.0.0.0:`. +- `p2p.max_num_outbound_peers` - the max number of outbound peer connections. **Advised to be `40`**. +- `p2p.persistent_peers` - the persistent peers. ⚠️ **Required to be `g18vg9lgndagym626q8jsgv2peyjatscykde3xju@devx-sen-1.test4.gnodevx.network:26656,g1fnwswr6p5nqfvusglv7g2vy0tzwt5npwe7stvv@devx-sen-2.test4.gnodevx.network:26656,g1xa78fprcqcejfpk8xeycd4hzxtg56w9qe29xky@103.219.168.237:26656,g1h8dsnzlv7r4skfuud38runjuk4dnxenpr79meg@72.46.84.19:26656,g1ppdm4s90txrxu027j5et4crmxmmr3qr3g4wgrp@186.233.184.76:26656,g1hta5u3vmt4k2gklu5ashsl9q0my8ykzqu60vme@103.14.26.13:26656,g1tace0q5t06y3fhk2473uekl5hg3rphghdy6ykp@163.172.20.47:26656,g17958rreg27qmhq27tjrkuc9q4sjx9dchwywxk3@185.194.217.143:26656`** ⚠️. +- `p2p.pex` - if using a sentry node architecture, should be `false`. **If not, please set to `true`**. +- `p2p.external_address` - the advertised peer dial address. If empty, will use the same port as the `p2p.laddr`. This + value should be **changed to `{{ your_ip_address }}:26656`** +- `rpc.laddr` - the JSON-RPC listen address, **specific to every node deployment**. +- `telemetry.enabled` - flag indicating if telemetry should be turned on. **Advised to be `true`**. +- `telemetry.exporter_endpoint` - endpoint for the otel exported. ⚠️ **Required if `telemetry.enabled=true`** ⚠️. +- `telemetry.service_instance_id` - unique ID of the node telemetry instance, **specific to every node deployment**. \ No newline at end of file diff --git a/misc/deployments/test4.gno.land/config.toml b/misc/deployments/test4.gno.land/config.toml new file mode 100644 index 00000000000..184ffc5a811 --- /dev/null +++ b/misc/deployments/test4.gno.land/config.toml @@ -0,0 +1,247 @@ + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# Database backend: goleveldb | boltdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +#* boltdb (uses etcd's fork of bolt - go.etcd.io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "db" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false +home = "" + +# A custom human readable name for this node +moniker = "artemis.local" # Change me! + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "secrets/node_key.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "secrets/priv_validator_key.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "secrets/priv_validator_state.json" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +##### consensus configuration options ##### +[consensus] + + # EmptyBlocks mode and possible interval between empty blocks + create_empty_blocks = true + create_empty_blocks_interval = "0s" + home = "" + + # Reactor sleep duration parameters + peer_gossip_sleep_duration = "100ms" + peer_query_maj23_sleep_duration = "2s" + + # Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) + skip_timeout_commit = false + timeout_commit = "2s" # Do NOT change me, leave me at 2s! + timeout_precommit = "1s" + timeout_precommit_delta = "500ms" + timeout_prevote = "1s" + timeout_prevote_delta = "500ms" + timeout_propose = "3s" + timeout_propose_delta = "500ms" + wal_file = "wal/cs.wal/wal" + +##### mempool configuration options ##### +[mempool] + broadcast = true + + # Size of the cache (used to filter transactions we saw earlier) in transactions + cache_size = 10000 + home = "" + + # Limit the total size of all txs in the mempool. + # This only accounts for raw transactions (e.g. given 1MB transactions and + # max_txs_bytes=5MB, mempool will only accept 5 transactions). + max_pending_txs_bytes = 1073741824 # ~1GB + recheck = true + + # Maximum number of transactions in the mempool + size = 10000 # Advised value is 10000 + wal_dir = "" + +##### peer to peer configuration options ##### +[p2p] + + # Toggle to disable guard against peers connecting from the same ip. + allow_duplicate_ip = false + dial_timeout = "3s" + + # Address to advertise to peers for them to dial + # If empty, will use the same port as the laddr, + # and will introspect on the listener or use UPnP + # to figure out the address. + external_address = "" # Change me! + + # Time to wait before flushing messages out on the connection + flush_throttle_timeout = "100ms" + + # Peer connection configuration. + handshake_timeout = "20s" + home = "" + + # Address to listen for incoming connections + laddr = "tcp://0.0.0.0:26656" # Change me! + + # Maximum number of inbound peers + max_num_inbound_peers = 40 + + # Maximum number of outbound peers to connect to, excluding persistent peers + max_num_outbound_peers = 40 # Advised value is 40 + + # Maximum size of a message packet payload, in bytes + max_packet_msg_payload_size = 1024 + + # Comma separated list of nodes to keep persistent connections to + persistent_peers = "g18vg9lgndagym626q8jsgv2peyjatscykde3xju@devx-sen-1.test4.gnodevx.network:26656,g1fnwswr6p5nqfvusglv7g2vy0tzwt5npwe7stvv@devx-sen-2.test4.gnodevx.network:26656,g1xa78fprcqcejfpk8xeycd4hzxtg56w9qe29xky@103.219.168.237:26656,g1h8dsnzlv7r4skfuud38runjuk4dnxenpr79meg@72.46.84.19:26656,g1ppdm4s90txrxu027j5et4crmxmmr3qr3g4wgrp@186.233.184.76:26656,g1hta5u3vmt4k2gklu5ashsl9q0my8ykzqu60vme@103.14.26.13:26656,g1tace0q5t06y3fhk2473uekl5hg3rphghdy6ykp@163.172.20.47:26656,g17958rreg27qmhq27tjrkuc9q4sjx9dchwywxk3@185.194.217.143:26656" + + # Set true to enable the peer-exchange reactor + pex = false # Should be `false` if using a sentry node. Otherwise `true`! + + # Comma separated list of peer IDs to keep private (will not be gossiped to other peers) + private_peer_ids = "" + + # Rate at which packets can be received, in bytes/second + recv_rate = 5120000 + + # Seed mode, in which node constantly crawls the network and looks for + # peers. If another node asks it for addresses, it responds and disconnects. + # + # Does not work if the peer-exchange reactor is disabled. + seed_mode = false + + # Comma separated list of seed nodes to connect to + seeds = "" + + # Rate at which packets can be sent, in bytes/second + send_rate = 5120000 + test_dial_fail = false + test_fuzz = false + + # UPNP port forwarding + upnp = false + + [p2p.test_fuzz_config] + MaxDelay = "3s" + Mode = 0 + ProbDropConn = 0.0 + ProbDropRW = 0.2 + ProbSleep = 0.0 + +##### rpc server configuration options ##### +[rpc] + + # A list of non simple headers the client is allowed to use with cross-domain requests + cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"] + + # A list of methods the client is allowed to use with cross-domain requests + cors_allowed_methods = ["HEAD", "GET", "POST", "OPTIONS"] + + # A list of origins a cross-domain request can be executed from + # Default value '[]' disables cors support + # Use '["*"]' to allow any origin + cors_allowed_origins = ["*"] + + # TCP or UNIX socket address for the gRPC server to listen on + # NOTE: This server only supports /broadcast_tx_commit + grpc_laddr = "" + + # Maximum number of simultaneous connections. + # Does not include RPC (HTTP&WebSocket) connections. See max_open_connections + # If you want to accept a larger number than the default, make sure + # you increase your OS limits. + # 0 - unlimited. + # Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} + # 1024 - 40 - 10 - 50 = 924 = ~900 + grpc_max_open_connections = 900 + home = "" + + # TCP or UNIX socket address for the RPC server to listen on + laddr = "tcp://0.0.0.0:26657" # Please use a reverse proxy! + + # Maximum size of request body, in bytes + max_body_bytes = 1000000 + + # Maximum size of request header, in bytes + max_header_bytes = 1048576 + + # Maximum number of simultaneous connections (including WebSocket). + # Does not include gRPC connections. See grpc_max_open_connections + # If you want to accept a larger number than the default, make sure + # you increase your OS limits. + # 0 - unlimited. + # Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} + # 1024 - 40 - 10 - 50 = 924 = ~900 + max_open_connections = 900 + + # How long to wait for a tx to be committed during /broadcast_tx_commit. + # WARNING: Using a value larger than 10s will result in increasing the + # global HTTP write timeout, which applies to all connections and endpoints. + # See https://github.com/tendermint/classic/issues/3435 + timeout_broadcast_tx_commit = "10s" + + # The path to a file containing certificate that is used to create the HTTPS server. + # Might be either absolute path or path related to tendermint's config directory. + # If the certificate is signed by a certificate authority, + # the certFile should be the concatenation of the server's certificate, any intermediates, + # and the CA's certificate. + # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. + tls_cert_file = "" + + # The path to a file containing matching private key that is used to create the HTTPS server. + # Might be either absolute path or path related to tendermint's config directory. + # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. + tls_key_file = "" + + # Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool + unsafe = false + +##### node telemetry ##### +[telemetry] + enabled = true # Advised to be `true` + + # the endpoint to export metrics to, like a local OpenTelemetry collector + exporter_endpoint = "" # Change me to the OTEL endpoint! + meter_name = "test4.gno.land" + + # the ID helps to distinguish instances of the same service that exist at the same time (e.g. instances of a horizontally scaled service) + service_instance_id = "gno-node-1" + service_name = "gno.land" + +##### event store ##### +[tx_event_store] + + # Type of event store + event_store_type = "none" + + # Event store parameters + [tx_event_store.event_store_params] diff --git a/misc/deployments/test4.gno.land/genesis.json b/misc/deployments/test4.gno.land/genesis.json new file mode 100644 index 00000000000..0c64b18ed42 --- /dev/null +++ b/misc/deployments/test4.gno.land/genesis.json @@ -0,0 +1,4478 @@ +{ + "genesis_time": "2024-07-10T06:00:00Z", + "chain_id": "test4", + "consensus_params": { + "Block": { + "MaxTxBytes": "1000000", + "MaxDataBytes": "2000000", + "MaxBlockBytes": "0", + "MaxGas": "100000000", + "TimeIotaMS": "100" + }, + "Validator": { + "PubKeyTypeURLs": [ + "/tm.PubKeyEd25519" + ] + } + }, + "validators": [ + { + "address": "g19v2h4pn6lrf8pwvhn8h0anek0cpt2tmhye4vkv", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "b0bw/4GEumi09c6uhTutrjXoV7lUAxGZQqcT+MbaFBQ=" + }, + "power": "1", + "name": "core-val-1" + }, + { + "address": "g13us7swtc9hq550y9v4z6vcarak9vf8nqdvcqj4", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "6FXDIqlSEl5pbPMMTLSa12XC99lWGQIRVm82bWTQibo=" + }, + "power": "1", + "name": "core-val-2" + }, + { + "address": "g1ectm6algkfw3qnjmjvx7hacmh358t36ggj5lqv", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "gqFJlETPoCVCnRnCVuRjpY47Yj2kxm5R2ukPFHtlLr4=" + }, + "power": "1", + "name": "core-val-3" + }, + { + "address": "g1tcxls3ylnrwrq95j33xpyuct4l370ra7jca4kj", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "n+j30mkr4dHWZlrq2MMrbR1YcIDDf3UiyEDF2oSFp3M=" + }, + "power": "1", + "name": "core-val-4" + }, + { + "address": "g1mxguhd5zacar64txhfm0v7hhtph5wur5hx86vs", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "C0l3PwIhnCmIG3YiAa9jf/uuvZj1ob7lolasnEhDgBE=" + }, + "power": "1", + "name": "devx-val-1" + }, + { + "address": "g1t9ctfa468hn6czff8kazw08crazehcxaqa2uaa", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "wBqj3F9RgIJ12UqGq5okzHfVbbd6H5AF5y4as8Ovx00=" + }, + "power": "1", + "name": "devx-val-2" + }, + { + "address": "g1gav33elude7prcdctpjekze7ft9l8qdjxqaj6d", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "+DJIo04cO4y+144EdXCw3ED/plkOhWDJ8FP1ScF40m4=" + }, + "power": "1", + "name": "onbloc-val-1" + } + ], + "app_hash": null, + "app_state": { + "@type": "/gno.GenesisState", + "balances": [ + "g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5=9000000000000000000ugnot", + "g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7=9000000000000000000ugnot", + "g1d7set9rsdw7ggt9elvzerep4jz80a4hu4crmzg=9000000000000000000ugnot", + "g13csus64lj8twrn266837l9e77dusk2zwa794qz=9000000000000000000ugnot", + "g1acn3xssksatydd0fcuslvgmjyw0fzkjdhusddg=9000000000000000000ugnot", + "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq=9000000000000000000ugnot", + "g1cm7u2fqd7drwfcqtn7zsgrfcyvlkkukhhtrxxj=9000000000000000000ugnot", + "g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j=9000000000000000000ugnot", + "g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu=9000000000000000000ugnot", + "g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun=9000000000000000000ugnot", + "g13d7jc32adhc39erm5me38w5v7ej7lpvlnqjk73=9000000000000000000ugnot", + "g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2=9000000000000000000ugnot", + "g1knpjkw7d5ft2830n8rnatllzq9gu85dmn20nrp=9000000000000000000ugnot", + "g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3=9000000000000000000ugnot", + "g1563d3cw0gdv68le6azw2s63xm0jx9xvgpmfatq=9000000000000000000ugnot", + "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=9000000000000000000ugnot", + "g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd=9000000000000000000ugnot", + "g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh=9000000000000000000ugnot", + "g1a5kdc6ykfykq9p6088sclnr7e63hj8y4nnr5gn=9000000000000000000ugnot", + "g16f5chytu99dmjqtekxf8qzg04vcv7dck6qny6d=9000000000000000000ugnot", + "g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl=9000000000000000000ugnot", + "g1gadp0yq5djelxwv7h8g7wpdf7x5w3vuzwmqrne=9000000000000000000ugnot", + "g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a=9000000000000000000ugnot", + "g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864=9000000000000000000ugnot", + "g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq=9000000000000000000ugnot", + "g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5=9000000000000000000ugnot", + "g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr=9000000000000000000ugnot", + "g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd=9000000000000000000ugnot", + "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5=9000000000000000000ugnot", + "g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm=9000000000000000000ugnot", + "g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7=9000000000000000000ugnot", + "g1s6ujq0r200a0tfqgnjfflz7ddetsnrwvzxxx74=9000000000000000000ugnot", + "g16n55jt73sl8s5kl3z5ahrf0qrlxp47n205j9ex=9000000000000000000ugnot", + "g1xja7p9s3ly3tvkgks9x0n6f6yau2hnzl4x8x3d=9000000000000000000ugnot", + "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=103000000ugnot", + "g1njagaeg7e398hze39ygfgvc4gwsh6lkz7dwnuz=9000000000000000000ugnot", + "g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x=9000000000000000000ugnot", + "g18l9us6trqaljw39j94wzf5ftxmd9qqkvrxghd2=9000000000000000000ugnot", + "g17p2kkyy9lp2z3ecw4psssk357vxp20afnyl00d=9000000000000000000ugnot", + "g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq=9000000000000000000ugnot", + "g1q6jrp203fq0239pv38sdq3y3urvd6vt5azacpv=9000000000000000000ugnot" + ], + "txs": [ + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "bank", + "path": "gno.land/p/demo/bank", + "files": [ + { + "name": "types.gno", + "body": "// TODO: this is an example, and needs to be fixed up and tested.\n\npackage bank\n\n// NOTE: unexposed struct for security.\ntype order struct {\n\tfrom Address\n\tto Address\n\tamount Coins\n\tprocessed bool\n}\n\n// NOTE: unexposed methods for security.\nfunc (ch *order) string() string {\n\treturn \"TODO\"\n}\n\n// Wraps the internal *order for external use.\ntype Order struct {\n\t*order\n}\n\n// XXX only exposed for demonstration. TODO unexpose, make full demo.\nfunc NewOrder(from Address, to Address, amount Coins) Order {\n\treturn Order{\n\t\torder: \u0026order{\n\t\t\tfrom: from,\n\t\t\tto: to,\n\t\t\tamount: amount,\n\t\t},\n\t}\n}\n\n// Panics if error, or already processed.\nfunc (o Order) Execute() {\n\tif o.order.processed {\n\t\tpanic(\"order already processed\")\n\t}\n\to.order.processed = true\n\t// TODO implemement.\n}\n\nfunc (o Order) IsZero() bool {\n\treturn o.order == nil\n}\n\nfunc (o Order) From() Address {\n\treturn o.order.from\n}\n\nfunc (o Order) To() Address {\n\treturn o.order.to\n}\n\nfunc (o Order) Amount() Coins {\n\treturn o.order.amount\n}\n\nfunc (o Order) Processed() bool {\n\treturn o.order.processed\n}\n\n//----------------------------------------\n// Escrow\n\ntype EscrowTerms struct {\n\tPartyA Address\n\tPartyB Address\n\tAmountA Coins\n\tAmountB Coins\n}\n\ntype EscrowContract struct {\n\tEscrowTerms\n\tOrderA Order\n\tOrderB Order\n}\n\nfunc CreateEscrow(terms EscrowTerms) *EscrowContract {\n\treturn \u0026EscrowContract{\n\t\tEscrowTerms: terms,\n\t}\n}\n\nfunc (esc *EscrowContract) SetOrderA(order Order) {\n\tif !esc.OrderA.IsZero() {\n\t\tpanic(\"order-a already set\")\n\t}\n\tif esc.EscrowTerms.PartyA != order.From() {\n\t\tpanic(\"invalid order-a:from mismatch\")\n\t}\n\tif esc.EscrowTerms.PartyB != order.To() {\n\t\tpanic(\"invalid order-a:to mismatch\")\n\t}\n\tif !esc.EscrowTerms.AmountA.Equal(order.Amount()) {\n\t\tpanic(\"invalid order-a amount\")\n\t}\n\tesc.OrderA = order\n}\n\nfunc (esc *EscrowContract) SetOrderB(order Order) {\n\tif !esc.OrderB.IsZero() {\n\t\tpanic(\"order-b already set\")\n\t}\n\tif esc.EscrowTerms.PartyB != order.From() {\n\t\tpanic(\"invalid order-b:from mismatch\")\n\t}\n\tif esc.EscrowTerms.PartyA != order.To() {\n\t\tpanic(\"invalid order-b:to mismatch\")\n\t}\n\tif !esc.EscrowTerms.AmountB.Equal(order.Amount()) {\n\t\tpanic(\"invalid order-b amount\")\n\t}\n\tesc.OrderA = order\n}\n\nfunc (esc *EscrowContract) Execute() {\n\tif esc.OrderA.IsZero() {\n\t\tpanic(\"order-a not yet set\")\n\t}\n\tif esc.OrderB.IsZero() {\n\t\tpanic(\"order-b not yet set\")\n\t}\n\t// NOTE: succeeds atomically.\n\tesc.OrderA.Execute()\n\tesc.OrderB.Execute()\n}\n\n//----------------------------------------\n// TODO: actually implement these in std package.\n\ntype (\n\tAddress string\n\tCoins []Coin\n\tCoin struct {\n\t\tDenom bool\n\t\tAmount int64\n\t}\n)\n\nfunc (a Coins) Equal(b Coins) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "avl", + "path": "gno.land/p/demo/avl", + "files": [ + { + "name": "node.gno", + "body": "package avl\n\n//----------------------------------------\n// Node\n\n// Node represents a node in an AVL tree.\ntype Node struct {\n\tkey string // key is the unique identifier for the node.\n\tvalue interface{} // value is the data stored in the node.\n\theight int8 // height is the height of the node in the tree.\n\tsize int // size is the number of nodes in the subtree rooted at this node.\n\tleftNode *Node // leftNode is the left child of the node.\n\trightNode *Node // rightNode is the right child of the node.\n}\n\n// NewNode creates a new node with the given key and value.\nfunc NewNode(key string, value interface{}) *Node {\n\treturn \u0026Node{\n\t\tkey: key,\n\t\tvalue: value,\n\t\theight: 0,\n\t\tsize: 1,\n\t}\n}\n\n// Size returns the size of the subtree rooted at the node.\nfunc (node *Node) Size() int {\n\tif node == nil {\n\t\treturn 0\n\t}\n\treturn node.size\n}\n\n// IsLeaf checks if the node is a leaf node (has no children).\nfunc (node *Node) IsLeaf() bool {\n\treturn node.height == 0\n}\n\n// Key returns the key of the node.\nfunc (node *Node) Key() string {\n\treturn node.key\n}\n\n// Value returns the value of the node.\nfunc (node *Node) Value() interface{} {\n\treturn node.value\n}\n\n// _copy creates a copy of the node (excluding value).\nfunc (node *Node) _copy() *Node {\n\tif node.height == 0 {\n\t\tpanic(\"Why are you copying a value node?\")\n\t}\n\treturn \u0026Node{\n\t\tkey: node.key,\n\t\theight: node.height,\n\t\tsize: node.size,\n\t\tleftNode: node.leftNode,\n\t\trightNode: node.rightNode,\n\t}\n}\n\n// Has checks if a node with the given key exists in the subtree rooted at the node.\nfunc (node *Node) Has(key string) (has bool) {\n\tif node == nil {\n\t\treturn false\n\t}\n\tif node.key == key {\n\t\treturn true\n\t}\n\tif node.height == 0 {\n\t\treturn false\n\t}\n\tif key \u003c node.key {\n\t\treturn node.getLeftNode().Has(key)\n\t}\n\treturn node.getRightNode().Has(key)\n}\n\n// Get searches for a node with the given key in the subtree rooted at the node\n// and returns its index, value, and whether it exists.\nfunc (node *Node) Get(key string) (index int, value interface{}, exists bool) {\n\tif node == nil {\n\t\treturn 0, nil, false\n\t}\n\n\tif node.height == 0 {\n\t\tif node.key == key {\n\t\t\treturn 0, node.value, true\n\t\t}\n\t\tif node.key \u003c key {\n\t\t\treturn 1, nil, false\n\t\t}\n\t\treturn 0, nil, false\n\t}\n\n\tif key \u003c node.key {\n\t\treturn node.getLeftNode().Get(key)\n\t}\n\n\trightNode := node.getRightNode()\n\tindex, value, exists = rightNode.Get(key)\n\tindex += node.size - rightNode.size\n\treturn index, value, exists\n}\n\n// GetByIndex retrieves the key-value pair of the node at the given index\n// in the subtree rooted at the node.\nfunc (node *Node) GetByIndex(index int) (key string, value interface{}) {\n\tif node.height == 0 {\n\t\tif index == 0 {\n\t\t\treturn node.key, node.value\n\t\t}\n\t\tpanic(\"GetByIndex asked for invalid index\")\n\t}\n\t// TODO: could improve this by storing the sizes\n\tleftNode := node.getLeftNode()\n\tif index \u003c leftNode.size {\n\t\treturn leftNode.GetByIndex(index)\n\t}\n\treturn node.getRightNode().GetByIndex(index - leftNode.size)\n}\n\n// Set inserts a new node with the given key-value pair into the subtree rooted at the node,\n// and returns the new root of the subtree and whether an existing node was updated.\n//\n// XXX consider a better way to do this... perhaps split Node from Node.\nfunc (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) {\n\tif node == nil {\n\t\treturn NewNode(key, value), false\n\t}\n\n\tif node.height == 0 {\n\t\treturn node.setLeaf(key, value)\n\t}\n\n\tnode = node._copy()\n\tif key \u003c node.key {\n\t\tnode.leftNode, updated = node.getLeftNode().Set(key, value)\n\t} else {\n\t\tnode.rightNode, updated = node.getRightNode().Set(key, value)\n\t}\n\n\tif updated {\n\t\treturn node, updated\n\t}\n\n\tnode.calcHeightAndSize()\n\treturn node.balance(), updated\n}\n\n// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node,\n// and returns the new root of the subtree and whether an existing node was updated.\nfunc (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) {\n\tif key == node.key {\n\t\treturn NewNode(key, value), true\n\t}\n\n\tif key \u003c node.key {\n\t\treturn \u0026Node{\n\t\t\tkey: node.key,\n\t\t\theight: 1,\n\t\t\tsize: 2,\n\t\t\tleftNode: NewNode(key, value),\n\t\t\trightNode: node,\n\t\t}, false\n\t}\n\n\treturn \u0026Node{\n\t\tkey: key,\n\t\theight: 1,\n\t\tsize: 2,\n\t\tleftNode: node,\n\t\trightNode: NewNode(key, value),\n\t}, false\n}\n\n// Remove deletes the node with the given key from the subtree rooted at the node.\n// returns the new root of the subtree, the new leftmost leaf key (if changed),\n// the removed value and the removal was successful.\nfunc (node *Node) Remove(key string) (\n\tnewNode *Node, newKey string, value interface{}, removed bool,\n) {\n\tif node == nil {\n\t\treturn nil, \"\", nil, false\n\t}\n\tif node.height == 0 {\n\t\tif key == node.key {\n\t\t\treturn nil, \"\", node.value, true\n\t\t}\n\t\treturn node, \"\", nil, false\n\t}\n\tif key \u003c node.key {\n\t\tvar newLeftNode *Node\n\t\tnewLeftNode, newKey, value, removed = node.getLeftNode().Remove(key)\n\t\tif !removed {\n\t\t\treturn node, \"\", value, false\n\t\t}\n\t\tif newLeftNode == nil { // left node held value, was removed\n\t\t\treturn node.rightNode, node.key, value, true\n\t\t}\n\t\tnode = node._copy()\n\t\tnode.leftNode = newLeftNode\n\t\tnode.calcHeightAndSize()\n\t\tnode = node.balance()\n\t\treturn node, newKey, value, true\n\t}\n\n\tvar newRightNode *Node\n\tnewRightNode, newKey, value, removed = node.getRightNode().Remove(key)\n\tif !removed {\n\t\treturn node, \"\", value, false\n\t}\n\tif newRightNode == nil { // right node held value, was removed\n\t\treturn node.leftNode, \"\", value, true\n\t}\n\tnode = node._copy()\n\tnode.rightNode = newRightNode\n\tif newKey != \"\" {\n\t\tnode.key = newKey\n\t}\n\tnode.calcHeightAndSize()\n\tnode = node.balance()\n\treturn node, \"\", value, true\n}\n\n// getLeftNode returns the left child of the node.\nfunc (node *Node) getLeftNode() *Node {\n\treturn node.leftNode\n}\n\n// getRightNode returns the right child of the node.\nfunc (node *Node) getRightNode() *Node {\n\treturn node.rightNode\n}\n\n// rotateRight performs a right rotation on the node and returns the new root.\n// NOTE: overwrites node\n// TODO: optimize balance \u0026 rotate\nfunc (node *Node) rotateRight() *Node {\n\tnode = node._copy()\n\tl := node.getLeftNode()\n\t_l := l._copy()\n\n\t_lrCached := _l.rightNode\n\t_l.rightNode = node\n\tnode.leftNode = _lrCached\n\n\tnode.calcHeightAndSize()\n\t_l.calcHeightAndSize()\n\n\treturn _l\n}\n\n// rotateLeft performs a left rotation on the node and returns the new root.\n// NOTE: overwrites node\n// TODO: optimize balance \u0026 rotate\nfunc (node *Node) rotateLeft() *Node {\n\tnode = node._copy()\n\tr := node.getRightNode()\n\t_r := r._copy()\n\n\t_rlCached := _r.leftNode\n\t_r.leftNode = node\n\tnode.rightNode = _rlCached\n\n\tnode.calcHeightAndSize()\n\t_r.calcHeightAndSize()\n\n\treturn _r\n}\n\n// calcHeightAndSize updates the height and size of the node based on its children.\n// NOTE: mutates height and size\nfunc (node *Node) calcHeightAndSize() {\n\tnode.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1\n\tnode.size = node.getLeftNode().size + node.getRightNode().size\n}\n\n// calcBalance calculates the balance factor of the node.\nfunc (node *Node) calcBalance() int {\n\treturn int(node.getLeftNode().height) - int(node.getRightNode().height)\n}\n\n// balance balances the subtree rooted at the node and returns the new root.\n// NOTE: assumes that node can be modified\n// TODO: optimize balance \u0026 rotate\nfunc (node *Node) balance() (newSelf *Node) {\n\tbalance := node.calcBalance()\n\tif balance \u003e= -1 {\n\t\treturn node\n\t}\n\tif balance \u003e 1 {\n\t\tif node.getLeftNode().calcBalance() \u003e= 0 {\n\t\t\t// Left Left Case\n\t\t\treturn node.rotateRight()\n\t\t}\n\t\t// Left Right Case\n\t\tleft := node.getLeftNode()\n\t\tnode.leftNode = left.rotateLeft()\n\t\treturn node.rotateRight()\n\t}\n\n\tif node.getRightNode().calcBalance() \u003c= 0 {\n\t\t// Right Right Case\n\t\treturn node.rotateLeft()\n\t}\n\n\t// Right Left Case\n\tright := node.getRightNode()\n\tnode.rightNode = right.rotateRight()\n\treturn node.rotateLeft()\n}\n\n// Shortcut for TraverseInRange.\nfunc (node *Node) Iterate(start, end string, cb func(*Node) bool) bool {\n\treturn node.TraverseInRange(start, end, true, true, cb)\n}\n\n// Shortcut for TraverseInRange.\nfunc (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool {\n\treturn node.TraverseInRange(start, end, false, true, cb)\n}\n\n// TraverseInRange traverses all nodes, including inner nodes.\n// Start is inclusive and end is exclusive when ascending,\n// Start and end are inclusive when descending.\n// Empty start and empty end denote no start and no end.\n// If leavesOnly is true, only visit leaf nodes.\n// NOTE: To simulate an exclusive reverse traversal,\n// just append 0x00 to start.\nfunc (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool {\n\tif node == nil {\n\t\treturn false\n\t}\n\tafterStart := (start == \"\" || start \u003c node.key)\n\tstartOrAfter := (start == \"\" || start \u003c= node.key)\n\tbeforeEnd := false\n\tif ascending {\n\t\tbeforeEnd = (end == \"\" || node.key \u003c end)\n\t} else {\n\t\tbeforeEnd = (end == \"\" || node.key \u003c= end)\n\t}\n\n\t// Run callback per inner/leaf node.\n\tstop := false\n\tif (!node.IsLeaf() \u0026\u0026 !leavesOnly) ||\n\t\t(node.IsLeaf() \u0026\u0026 startOrAfter \u0026\u0026 beforeEnd) {\n\t\tstop = cb(node)\n\t\tif stop {\n\t\t\treturn stop\n\t\t}\n\t}\n\tif node.IsLeaf() {\n\t\treturn stop\n\t}\n\n\tif ascending {\n\t\t// check lower nodes, then higher\n\t\tif afterStart {\n\t\t\tstop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t\tif stop {\n\t\t\treturn stop\n\t\t}\n\t\tif beforeEnd {\n\t\t\tstop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t} else {\n\t\t// check the higher nodes first\n\t\tif beforeEnd {\n\t\t\tstop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t\tif stop {\n\t\t\treturn stop\n\t\t}\n\t\tif afterStart {\n\t\t\tstop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t}\n\n\treturn stop\n}\n\n// TraverseByOffset traverses all nodes, including inner nodes.\n// A limit of math.MaxInt means no limit.\nfunc (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool {\n\tif node == nil {\n\t\treturn false\n\t}\n\n\t// fast paths. these happen only if TraverseByOffset is called directly on a leaf.\n\tif limit \u003c= 0 || offset \u003e= node.size {\n\t\treturn false\n\t}\n\tif node.IsLeaf() {\n\t\tif offset \u003e 0 {\n\t\t\treturn false\n\t\t}\n\t\treturn cb(node)\n\t}\n\n\t// go to the actual recursive function.\n\treturn node.traverseByOffset(offset, limit, descending, leavesOnly, cb)\n}\n\n// TraverseByOffset traverses the subtree rooted at the node by offset and limit,\n// in either ascending or descending order, and applies the callback function to each traversed node.\n// If leavesOnly is true, only leaf nodes are visited.\nfunc (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool {\n\t// caller guarantees: offset \u003c node.size; limit \u003e 0.\n\tif !leavesOnly {\n\t\tif cb(node) {\n\t\t\treturn true\n\t\t}\n\t}\n\tfirst, second := node.getLeftNode(), node.getRightNode()\n\tif descending {\n\t\tfirst, second = second, first\n\t}\n\tif first.IsLeaf() {\n\t\t// either run or skip, based on offset\n\t\tif offset \u003e 0 {\n\t\t\toffset--\n\t\t} else {\n\t\t\tcb(first)\n\t\t\tlimit--\n\t\t\tif limit \u003c= 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// possible cases:\n\t\t// 1 the offset given skips the first node entirely\n\t\t// 2 the offset skips none or part of the first node, but the limit requires some of the second node.\n\t\t// 3 the offset skips none or part of the first node, and the limit stops our search on the first node.\n\t\tif offset \u003e= first.size {\n\t\t\toffset -= first.size // 1\n\t\t} else {\n\t\t\tif first.traverseByOffset(offset, limit, descending, leavesOnly, cb) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// number of leaves which could actually be called from inside\n\t\t\tdelta := first.size - offset\n\t\t\toffset = 0\n\t\t\tif delta \u003e= limit {\n\t\t\t\treturn true // 3\n\t\t\t}\n\t\t\tlimit -= delta // 2\n\t\t}\n\t}\n\n\t// because of the caller guarantees and the way we handle the first node,\n\t// at this point we know that limit \u003e 0 and there must be some values in\n\t// this second node that we include.\n\n\t// =\u003e if the second node is a leaf, it has to be included.\n\tif second.IsLeaf() {\n\t\treturn cb(second)\n\t}\n\t// =\u003e if it is not a leaf, it will still be enough to recursively call this\n\t// function with the updated offset and limit\n\treturn second.traverseByOffset(offset, limit, descending, leavesOnly, cb)\n}\n\n// Only used in testing...\nfunc (node *Node) lmd() *Node {\n\tif node.height == 0 {\n\t\treturn node\n\t}\n\treturn node.getLeftNode().lmd()\n}\n\n// Only used in testing...\nfunc (node *Node) rmd() *Node {\n\tif node.height == 0 {\n\t\treturn node\n\t}\n\treturn node.getRightNode().rmd()\n}\n\nfunc maxInt8(a, b int8) int8 {\n\tif a \u003e b {\n\t\treturn a\n\t}\n\treturn b\n}\n" + }, + { + "name": "node_test.gno", + "body": "package avl\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestTraverseByOffset(t *testing.T) {\n\tconst testStrings = `Alfa\nAlfred\nAlpha\nAlphabet\nBeta\nBeth\nBook\nBrowser`\n\ttt := []struct {\n\t\tname string\n\t\tdesc bool\n\t}{\n\t\t{\"ascending\", false},\n\t\t{\"descending\", true},\n\t}\n\n\tfor _, tt := range tt {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsl := strings.Split(testStrings, \"\\n\")\n\n\t\t\t// sort a first time in the order opposite to how we'll be traversing\n\t\t\t// the tree, to ensure that we are not just iterating through with\n\t\t\t// insertion order.\n\t\t\tsort.Strings(sl)\n\t\t\tif !tt.desc {\n\t\t\t\treverseSlice(sl)\n\t\t\t}\n\n\t\t\tr := NewNode(sl[0], nil)\n\t\t\tfor _, v := range sl[1:] {\n\t\t\t\tr, _ = r.Set(v, nil)\n\t\t\t}\n\n\t\t\t// then sort sl in the order we'll be traversing it, so that we can\n\t\t\t// compare the result with sl.\n\t\t\treverseSlice(sl)\n\n\t\t\tvar result []string\n\t\t\tfor i := 0; i \u003c len(sl); i++ {\n\t\t\t\tr.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif !slicesEqual(sl, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", sl, result)\n\t\t\t}\n\n\t\t\tfor l := 2; l \u003c= len(sl); l++ {\n\t\t\t\t// \"slices\"\n\t\t\t\tfor i := 0; i \u003c= len(sl); i++ {\n\t\t\t\t\tmax := i + l\n\t\t\t\t\tif max \u003e len(sl) {\n\t\t\t\t\t\tmax = len(sl)\n\t\t\t\t\t}\n\t\t\t\t\texp := sl[i:max]\n\t\t\t\t\tactual := []string{}\n\n\t\t\t\t\tr.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool {\n\t\t\t\t\t\tactual = append(actual, tr.Key())\n\t\t\t\t\t\treturn false\n\t\t\t\t\t})\n\t\t\t\t\tif !slicesEqual(exp, actual) {\n\t\t\t\t\t\tt.Errorf(\"want %v got %v\", exp, actual)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHas(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\thasKey string\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\t\"has key in non-empty tree\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"B\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"does not have key in non-empty tree\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"F\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"has key in single-node tree\",\n\t\t\t[]string{\"A\"},\n\t\t\t\"A\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"does not have key in single-node tree\",\n\t\t\t[]string{\"A\"},\n\t\t\t\"B\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"does not have key in empty tree\",\n\t\t\t[]string{},\n\t\t\t\"A\",\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tresult := tree.Has(tt.hasKey)\n\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Expected %v, got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGet(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\tgetKey string\n\t\texpectIdx int\n\t\texpectVal interface{}\n\t\texpectExists bool\n\t}{\n\t\t{\n\t\t\t\"get existing key\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"B\",\n\t\t\t1,\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"get non-existent key (smaller)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"@\",\n\t\t\t0,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get non-existent key (larger)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"F\",\n\t\t\t5,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get from empty tree\",\n\t\t\t[]string{},\n\t\t\t\"A\",\n\t\t\t0,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tidx, val, exists := tree.Get(tt.getKey)\n\n\t\t\tif idx != tt.expectIdx {\n\t\t\t\tt.Errorf(\"Expected index %d, got %d\", tt.expectIdx, idx)\n\t\t\t}\n\n\t\t\tif val != tt.expectVal {\n\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expectVal, val)\n\t\t\t}\n\n\t\t\tif exists != tt.expectExists {\n\t\t\t\tt.Errorf(\"Expected exists %t, got %t\", tt.expectExists, exists)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetByIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\tidx int\n\t\texpectKey string\n\t\texpectVal interface{}\n\t\texpectPanic bool\n\t}{\n\t\t{\n\t\t\t\"get by valid index\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t2,\n\t\t\t\"C\",\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get by valid index (smallest)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t0,\n\t\t\t\"A\",\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get by valid index (largest)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t4,\n\t\t\t\"E\",\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get by invalid index (negative)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t-1,\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"get by invalid index (out of range)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t5,\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tif tt.expectPanic {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\t\tt.Errorf(\"Expected a panic but didn't get one\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\tkey, val := tree.GetByIndex(tt.idx)\n\n\t\t\tif !tt.expectPanic {\n\t\t\t\tif key != tt.expectKey {\n\t\t\t\t\tt.Errorf(\"Expected key %s, got %s\", tt.expectKey, key)\n\t\t\t\t}\n\n\t\t\t\tif val != tt.expectVal {\n\t\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expectVal, val)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRemove(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\tremoveKey string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"remove leaf node\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"D\"},\n\t\t\t\"B\",\n\t\t\t[]string{\"A\", \"C\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"remove node with one child\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"D\"},\n\t\t\t\"A\",\n\t\t\t[]string{\"B\", \"C\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"remove node with two children\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"C\",\n\t\t\t[]string{\"A\", \"B\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"remove root node\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"C\",\n\t\t\t[]string{\"A\", \"B\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"remove non-existent key\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"F\",\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\ttree, _, _, _ = tree.Remove(tt.removeKey)\n\n\t\t\tresult := make([]string, 0)\n\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\tresult = append(result, n.Key())\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraverse(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"empty tree\",\n\t\t\t[]string{},\n\t\t\t[]string{},\n\t\t},\n\t\t{\n\t\t\t\"single node tree\",\n\t\t\t[]string{\"A\"},\n\t\t\t[]string{\"A\"},\n\t\t},\n\t\t{\n\t\t\t\"small tree\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"large tree\",\n\t\t\t[]string{\"H\", \"D\", \"L\", \"B\", \"F\", \"J\", \"N\", \"A\", \"C\", \"E\", \"G\", \"I\", \"K\", \"M\", \"O\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tt.Run(\"iterate\", func(t *testing.T) {\n\t\t\t\tvar result []string\n\t\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"ReverseIterate\", func(t *testing.T) {\n\t\t\t\tvar result []string\n\t\t\t\ttree.ReverseIterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t\texpected := make([]string, len(tt.expected))\n\t\t\t\tcopy(expected, tt.expected)\n\t\t\t\tfor i, j := 0, len(expected)-1; i \u003c j; i, j = i+1, j-1 {\n\t\t\t\t\texpected[i], expected[j] = expected[j], expected[i]\n\t\t\t\t}\n\t\t\t\tif !slicesEqual(expected, result) {\n\t\t\t\t\tt.Errorf(\"want %v got %v\", expected, result)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"TraverseInRange\", func(t *testing.T) {\n\t\t\t\tvar result []string\n\t\t\t\tstart, end := \"C\", \"M\"\n\t\t\t\ttree.TraverseInRange(start, end, true, true, func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t\texpected := make([]string, 0)\n\t\t\t\tfor _, key := range tt.expected {\n\t\t\t\t\tif key \u003e= start \u0026\u0026 key \u003c end {\n\t\t\t\t\t\texpected = append(expected, key)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !slicesEqual(expected, result) {\n\t\t\t\t\tt.Errorf(\"want %v got %v\", expected, result)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestRotateWhenHeightDiffers(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"right rotation when left subtree is higher\",\n\t\t\t[]string{\"E\", \"C\", \"A\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"E\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"left rotation when right subtree is higher\",\n\t\t\t[]string{\"A\", \"C\", \"E\", \"D\", \"F\"},\n\t\t\t[]string{\"A\", \"C\", \"D\", \"E\", \"F\"},\n\t\t},\n\t\t{\n\t\t\t\"left-right rotation\",\n\t\t\t[]string{\"E\", \"A\", \"C\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"E\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"right-left rotation\",\n\t\t\t[]string{\"A\", \"E\", \"C\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"E\", \"D\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\t// perform rotation or balance\n\t\t\ttree = tree.balance()\n\n\t\t\t// check tree structure\n\t\t\tvar result []string\n\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\tresult = append(result, n.Key())\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRotateAndBalance(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"right rotation\",\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"left rotation\",\n\t\t\t[]string{\"E\", \"D\", \"C\", \"B\", \"A\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"left-right rotation\",\n\t\t\t[]string{\"C\", \"A\", \"E\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"right-left rotation\",\n\t\t\t[]string{\"C\", \"E\", \"A\", \"D\", \"B\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\ttree = tree.balance()\n\n\t\t\tvar result []string\n\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\tresult = append(result, n.Key())\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc slicesEqual(w1, w2 []string) bool {\n\tif len(w1) != len(w2) {\n\t\treturn false\n\t}\n\tfor i := 0; i \u003c len(w1); i++ {\n\t\tif w1[0] != w2[0] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc maxint8(a, b int8) int8 {\n\tif a \u003e b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc reverseSlice(ss []string) {\n\tfor i := 0; i \u003c len(ss)/2; i++ {\n\t\tj := len(ss) - 1 - i\n\t\tss[i], ss[j] = ss[j], ss[i]\n\t}\n}\n" + }, + { + "name": "tree.gno", + "body": "package avl\n\ntype IterCbFn func(key string, value interface{}) bool\n\n//----------------------------------------\n// Tree\n\n// The zero struct can be used as an empty tree.\ntype Tree struct {\n\tnode *Node\n}\n\n// NewTree creates a new empty AVL tree.\nfunc NewTree() *Tree {\n\treturn \u0026Tree{\n\t\tnode: nil,\n\t}\n}\n\n// Size returns the number of key-value pair in the tree.\nfunc (tree *Tree) Size() int {\n\treturn tree.node.Size()\n}\n\n// Has checks whether a key exists in the tree.\n// It returns true if the key exists, otherwise false.\nfunc (tree *Tree) Has(key string) (has bool) {\n\treturn tree.node.Has(key)\n}\n\n// Get retrieves the value associated with the given key.\n// It returns the value and a boolean indicating whether the key exists.\nfunc (tree *Tree) Get(key string) (value interface{}, exists bool) {\n\t_, value, exists = tree.node.Get(key)\n\treturn\n}\n\n// GetByIndex retrieves the key-value pair at the specified index in the tree.\n// It returns the key and value at the given index.\nfunc (tree *Tree) GetByIndex(index int) (key string, value interface{}) {\n\treturn tree.node.GetByIndex(index)\n}\n\n// Set inserts a key-value pair into the tree.\n// If the key already exists, the value will be updated.\n// It returns a boolean indicating whether the key was newly inserted or updated.\nfunc (tree *Tree) Set(key string, value interface{}) (updated bool) {\n\tnewnode, updated := tree.node.Set(key, value)\n\ttree.node = newnode\n\treturn updated\n}\n\n// Remove removes a key-value pair from the tree.\n// It returns the removed value and a boolean indicating whether the key was found and removed.\nfunc (tree *Tree) Remove(key string) (value interface{}, removed bool) {\n\tnewnode, _, value, removed := tree.node.Remove(key)\n\ttree.node = newnode\n\treturn value, removed\n}\n\n// Iterate performs an in-order traversal of the tree within the specified key range.\n// It calls the provided callback function for each key-value pair encountered.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) Iterate(start, end string, cb IterCbFn) bool {\n\treturn tree.node.TraverseInRange(start, end, true, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n\n// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range.\n// It calls the provided callback function for each key-value pair encountered.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool {\n\treturn tree.node.TraverseInRange(start, end, false, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n\n// IterateByOffset performs an in-order traversal of the tree starting from the specified offset.\n// It calls the provided callback function for each key-value pair encountered, up to the specified count.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool {\n\treturn tree.node.TraverseByOffset(offset, count, true, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n\n// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset.\n// It calls the provided callback function for each key-value pair encountered, up to the specified count.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool {\n\treturn tree.node.TraverseByOffset(offset, count, false, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n" + }, + { + "name": "tree_test.gno", + "body": "package avl\n\nimport \"testing\"\n\nfunc TestNewTree(t *testing.T) {\n\ttree := NewTree()\n\tif tree.node != nil {\n\t\tt.Error(\"Expected tree.node to be nil\")\n\t}\n}\n\nfunc TestTreeSize(t *testing.T) {\n\ttree := NewTree()\n\tif tree.Size() != 0 {\n\t\tt.Error(\"Expected empty tree size to be 0\")\n\t}\n\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\tif tree.Size() != 2 {\n\t\tt.Error(\"Expected tree size to be 2\")\n\t}\n}\n\nfunc TestTreeHas(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\n\tif !tree.Has(\"key1\") {\n\t\tt.Error(\"Expected tree to have key1\")\n\t}\n\n\tif tree.Has(\"key2\") {\n\t\tt.Error(\"Expected tree to not have key2\")\n\t}\n}\n\nfunc TestTreeGet(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\n\tvalue, exists := tree.Get(\"key1\")\n\tif !exists || value != \"value1\" {\n\t\tt.Error(\"Expected Get to return value1 and true\")\n\t}\n\n\t_, exists = tree.Get(\"key2\")\n\tif exists {\n\t\tt.Error(\"Expected Get to return false for non-existent key\")\n\t}\n}\n\nfunc TestTreeGetByIndex(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\n\tkey, value := tree.GetByIndex(0)\n\tif key != \"key1\" || value != \"value1\" {\n\t\tt.Error(\"Expected GetByIndex(0) to return key1 and value1\")\n\t}\n\n\tkey, value = tree.GetByIndex(1)\n\tif key != \"key2\" || value != \"value2\" {\n\t\tt.Error(\"Expected GetByIndex(1) to return key2 and value2\")\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Error(\"Expected GetByIndex to panic for out-of-range index\")\n\t\t}\n\t}()\n\ttree.GetByIndex(2)\n}\n\nfunc TestTreeRemove(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\n\tvalue, removed := tree.Remove(\"key1\")\n\tif !removed || value != \"value1\" || tree.Size() != 0 {\n\t\tt.Error(\"Expected Remove to remove key-value pair\")\n\t}\n\n\t_, removed = tree.Remove(\"key2\")\n\tif removed {\n\t\tt.Error(\"Expected Remove to return false for non-existent key\")\n\t}\n}\n\nfunc TestTreeIterate(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key1\", \"key2\", \"key3\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n\nfunc TestTreeReverseIterate(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key3\", \"key2\", \"key1\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n\nfunc TestTreeIterateByOffset(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.IterateByOffset(1, 2, func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key2\", \"key3\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n\nfunc TestTreeReverseIterateByOffset(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.ReverseIterateByOffset(1, 2, func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key2\", \"key1\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n" + }, + { + "name": "z_0_filetest.gno", + "body": "// PKGPATH: gno.land/r/test\npackage test\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\nvar node *avl.Node\n\nfunc init() {\n\tnode = avl.NewNode(\"key0\", \"value0\")\n\t// node, _ = node.Set(\"key0\", \"value0\")\n}\n\nfunc main() {\n\tvar updated bool\n\tnode, updated = node.Set(\"key1\", \"value1\")\n\t// println(node, updated)\n\tprintln(updated, node.Size())\n}\n\n// Output:\n// false 2\n\n// Realm:\n// switchrealm[\"gno.land/r/test\"]\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"627e8e517e7ae5db0f3b753e2a32b607989198b6\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:5\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"value1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b28057ab7be6383785c0a5503e8a531bdbc21851\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:9\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"6da365f0d6cacbcdf53cd5a4b125803cddce08c2\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:4\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f216afe7b5a17f4ebdbb98dceccedbc22e237596\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ff1a50d8489090af37a2c7766d659f0d717939b5\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\"\n// }\n// }\n// }\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={\n// \"Blank\": {},\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"5\",\n// \"RefCount\": \"2\"\n// },\n// \"Parent\": null,\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"0\",\n// \"File\": \"\",\n// \"Line\": \"0\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Values\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"init.1\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"10\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"main\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"15\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// }\n// ]\n// }\n" + }, + { + "name": "z_1_filetest.gno", + "body": "// PKGPATH: gno.land/r/test\npackage test\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\nvar node *avl.Node\n\nfunc init() {\n\tnode = avl.NewNode(\"key0\", \"value0\")\n\tnode, _ = node.Set(\"key1\", \"value1\")\n}\n\nfunc main() {\n\tvar updated bool\n\tnode, updated = node.Set(\"key2\", \"value2\")\n\t// println(node, updated)\n\tprintln(updated, node.Size())\n}\n\n// Output:\n// false 3\n\n// Realm:\n// switchrealm[\"gno.land/r/test\"]\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"value2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"143aebc820da33550f7338723fb1e2eec575b196\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"2e733a8e9e74fe14f0a5d10fb0f6728fa53d052d\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"fe20a19f956511f274dc77854e9e5468387260f4\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AwAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c89a71bdf045e8bde2059dc9d33839f916e02e5d\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"90fa67f8c47db4b9b2a60425dff08d5a3385100f\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"83e42caaf53070dd95b5f859053eb51ed900bbda\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\"\n// }\n// }\n// }\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={\n// \"Blank\": {},\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"9\",\n// \"RefCount\": \"2\"\n// },\n// \"Parent\": null,\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"0\",\n// \"File\": \"\",\n// \"Line\": \"0\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Values\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1faa9fa4ba1935121a6d3f0a623772e9d4499b0a\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"init.1\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"10\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"main\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"15\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// }\n// ]\n// }\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]\n" + }, + { + "name": "z_2_filetest.gno", + "body": "// PKGPATH: gno.land/r/test\npackage test\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\nvar tree avl.Tree\n\nfunc init() {\n\ttree.Set(\"key0\", \"value0\")\n\ttree.Set(\"key1\", \"value1\")\n}\n\nfunc main() {\n\tvar updated bool\n\tupdated = tree.Set(\"key2\", \"value2\")\n\tprintln(updated, tree.Size())\n}\n\n// Output:\n// false 3\n\n// Realm:\n// switchrealm[\"gno.land/r/test\"]\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:16]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"value2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:16\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"db333c89cd6773709e031f1f4e4ed4d3fed66c11\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:16\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"849a50d6c78d65742752e3c89ad8dd556e2e63cb\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:9\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b4fc2fdd2d0fe936c87ed2ace97136cffeed207f\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a1160b0060ad752dbfe5fe436f7734bb19136150\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AwAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"fd95e08763159ac529e26986d652e752e78b6325\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3ecdcf148fe2f9e97b72a3bedf303b2ba56d4f4b\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"63126557dba88f8556f7a0ccbbfc1d218ae7a302\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\"\n// }\n// }\n// }\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"d31c7e797793e03ffe0bbcb72f963264f8300d22\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\",\n// \"ModTime\": \"10\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"RefCount\": \"1\"\n// }\n// }\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "testutils", + "path": "gno.land/p/demo/testutils", + "files": [ + { + "name": "access.gno", + "body": "package testutils\n\n// for testing access. see tests/files/access*.go\n\n// NOTE: non-package variables cannot be overridden, except during init().\nvar (\n\tTestVar1 int\n\ttestVar2 int\n)\n\nfunc init() {\n\tTestVar1 = 123\n\ttestVar2 = 456\n}\n\ntype TestAccessStruct struct {\n\tPublicField string\n\tprivateField string\n}\n\nfunc (tas TestAccessStruct) PublicMethod() string {\n\treturn tas.PublicField + \"/\" + tas.privateField\n}\n\nfunc (tas TestAccessStruct) privateMethod() string {\n\treturn tas.PublicField + \"/\" + tas.privateField\n}\n\nfunc NewTestAccessStruct(pub, priv string) TestAccessStruct {\n\treturn TestAccessStruct{\n\t\tPublicField: pub,\n\t\tprivateField: priv,\n\t}\n}\n\n// see access6.g0 etc.\ntype PrivateInterface interface {\n\tprivateMethod() string\n}\n\nfunc PrintPrivateInterface(pi PrivateInterface) {\n\tprintln(\"testutils.PrintPrivateInterface\", pi.privateMethod())\n}\n" + }, + { + "name": "crypto.gno", + "body": "package testutils\n\nimport \"std\"\n\nfunc TestAddress(name string) std.Address {\n\tif len(name) \u003e std.RawAddressSize {\n\t\tpanic(\"address name cannot be greater than std.AddressSize bytes\")\n\t}\n\taddr := std.RawAddress{}\n\t// TODO: use strings.RepeatString or similar.\n\t// NOTE: I miss python's \"\".Join().\n\tblanks := \"____________________\"\n\tcopy(addr[:], []byte(blanks))\n\tcopy(addr[:], []byte(name))\n\treturn std.Address(std.EncodeBech32(\"g\", addr))\n}\n" + }, + { + "name": "misc.gno", + "body": "package testutils\n\n// For testing std.GetCallerAt().\nfunc WrapCall(fn func()) {\n\tfn()\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "acl", + "path": "gno.land/p/demo/acl", + "files": [ + { + "name": "acl.gno", + "body": "package acl\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nfunc New() *Directory {\n\treturn \u0026Directory{\n\t\tuserGroups: avl.Tree{},\n\t\tpermBuckets: avl.Tree{},\n\t}\n}\n\ntype Directory struct {\n\tpermBuckets avl.Tree // identifier -\u003e perms\n\tuserGroups avl.Tree // std.Address -\u003e []string\n}\n\nfunc (d *Directory) HasPerm(addr std.Address, verb, resource string) bool {\n\t// FIXME: consider memoize.\n\n\t// user perms\n\tif d.getBucketPerms(\"u:\"+addr.String()).hasPerm(verb, resource) {\n\t\treturn true\n\t}\n\n\t// everyone's perms.\n\tif d.getBucketPerms(\"g:\"+Everyone).hasPerm(verb, resource) {\n\t\treturn true\n\t}\n\n\t// user groups' perms.\n\tgroups, ok := d.userGroups.Get(addr.String())\n\tif ok {\n\t\tfor _, group := range groups.([]string) {\n\t\t\tif d.getBucketPerms(\"g:\"+group).hasPerm(verb, resource) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (d *Directory) getBucketPerms(bucket string) perms {\n\tres, ok := d.permBuckets.Get(bucket)\n\tif ok {\n\t\treturn res.(perms)\n\t}\n\treturn perms{}\n}\n\nfunc (d *Directory) HasRole(addr std.Address, role string) bool {\n\treturn d.HasPerm(addr, \"role\", role)\n}\n\nfunc (d *Directory) AddUserPerm(addr std.Address, verb, resource string) {\n\tbucket := \"u:\" + addr.String()\n\tp := perm{\n\t\tverbs: []string{verb},\n\t\tresources: []string{resource},\n\t}\n\td.addPermToBucket(bucket, p)\n}\n\nfunc (d *Directory) AddGroupPerm(name string, verb, resource string) {\n\tbucket := \"g:\" + name\n\tp := perm{\n\t\tverbs: []string{verb},\n\t\tresources: []string{resource},\n\t}\n\td.addPermToBucket(bucket, p)\n}\n\nfunc (d *Directory) addPermToBucket(bucket string, p perm) {\n\tvar ps perms\n\n\texisting, ok := d.permBuckets.Get(bucket)\n\tif ok {\n\t\tps = existing.(perms)\n\t}\n\tps = append(ps, p)\n\n\td.permBuckets.Set(bucket, ps)\n}\n\nfunc (d *Directory) AddUserToGroup(user std.Address, group string) {\n\texisting, ok := d.userGroups.Get(user.String())\n\tvar groups []string\n\tif ok {\n\t\tgroups = existing.([]string)\n\t}\n\tgroups = append(groups, group)\n\td.userGroups.Set(user.String(), groups)\n}\n\n// TODO: helpers to remove permissions.\n// TODO: helpers to adds multiple permissions at once -\u003e {verbs: []string{\"read\",\"write\"}}.\n// TODO: helpers to delete users from gorups.\n// TODO: helpers to quickly reset states.\n" + }, + { + "name": "acl_test.gno", + "body": "package acl\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc Test(t *testing.T) {\n\tadm := testutils.TestAddress(\"admin\")\n\tmod := testutils.TestAddress(\"mod\")\n\tusr := testutils.TestAddress(\"user\")\n\tcst := testutils.TestAddress(\"custom\")\n\n\tdir := New()\n\n\t// by default, no one has perm.\n\tshouldNotHasRole(t, dir, adm, \"foo\")\n\tshouldNotHasRole(t, dir, mod, \"foo\")\n\tshouldNotHasRole(t, dir, usr, \"foo\")\n\tshouldNotHasRole(t, dir, cst, \"foo\")\n\tshouldNotHasPerm(t, dir, adm, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, adm, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"read\", \"r/demo/boards:gnolang/1\")\n\n\t// adding all the rights to admin.\n\tdir.AddUserPerm(adm, \".*\", \".*\")\n\tshouldHasRole(t, dir, adm, \"foo\")\n\tshouldNotHasRole(t, dir, mod, \"foo\")\n\tshouldNotHasRole(t, dir, usr, \"foo\")\n\tshouldNotHasRole(t, dir, cst, \"foo\")\n\tshouldHasPerm(t, dir, adm, \"write\", \"r/demo/boards:gnolang/1\") // new\n\tshouldNotHasPerm(t, dir, mod, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, adm, \"read\", \"r/demo/boards:gnolang/1\") // new\n\tshouldNotHasPerm(t, dir, mod, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"read\", \"r/demo/boards:gnolang/1\")\n\n\t// adding custom regexp rule for user \"cst\".\n\tdir.AddUserPerm(cst, \"write\", \"r/demo/boards:gnolang/.*\")\n\tshouldHasRole(t, dir, adm, \"foo\")\n\tshouldNotHasRole(t, dir, mod, \"foo\")\n\tshouldNotHasRole(t, dir, usr, \"foo\")\n\tshouldNotHasRole(t, dir, cst, \"foo\")\n\tshouldHasPerm(t, dir, adm, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, cst, \"write\", \"r/demo/boards:gnolang/1\") // new\n\tshouldHasPerm(t, dir, adm, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"read\", \"r/demo/boards:gnolang/1\")\n\n\t// adding a group perm for a new group.\n\t// no changes expected.\n\tdir.AddGroupPerm(\"mods\", \"role\", \"moderator\")\n\tdir.AddGroupPerm(\"mods\", \"write\", \".*\")\n\tshouldHasRole(t, dir, adm, \"foo\")\n\tshouldNotHasRole(t, dir, mod, \"foo\")\n\tshouldNotHasRole(t, dir, usr, \"foo\")\n\tshouldNotHasRole(t, dir, cst, \"foo\")\n\tshouldHasPerm(t, dir, adm, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, cst, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, adm, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"read\", \"r/demo/boards:gnolang/1\")\n\n\t// assigning the user \"mod\" to the \"mods\" group.\n\tdir.AddUserToGroup(mod, \"mods\")\n\tshouldHasRole(t, dir, adm, \"foo\")\n\tshouldNotHasRole(t, dir, mod, \"foo\")\n\tshouldNotHasRole(t, dir, usr, \"foo\")\n\tshouldNotHasRole(t, dir, cst, \"foo\")\n\tshouldHasPerm(t, dir, adm, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, mod, \"write\", \"r/demo/boards:gnolang/1\") // new\n\tshouldNotHasPerm(t, dir, usr, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, cst, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, adm, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, mod, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, cst, \"read\", \"r/demo/boards:gnolang/1\")\n\n\t// adding \"read\" permission for everyone.\n\tdir.AddGroupPerm(Everyone, \"read\", \".*\")\n\tshouldHasRole(t, dir, adm, \"foo\")\n\tshouldNotHasRole(t, dir, mod, \"foo\")\n\tshouldNotHasRole(t, dir, usr, \"foo\")\n\tshouldNotHasRole(t, dir, cst, \"foo\")\n\tshouldHasPerm(t, dir, adm, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, mod, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldNotHasPerm(t, dir, usr, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, cst, \"write\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, adm, \"read\", \"r/demo/boards:gnolang/1\")\n\tshouldHasPerm(t, dir, mod, \"read\", \"r/demo/boards:gnolang/1\") // new\n\tshouldHasPerm(t, dir, usr, \"read\", \"r/demo/boards:gnolang/1\") // new\n\tshouldHasPerm(t, dir, cst, \"read\", \"r/demo/boards:gnolang/1\") // new\n}\n\nfunc shouldHasRole(t *testing.T, dir *Directory, addr std.Address, role string) {\n\tt.Helper()\n\tcheck := dir.HasRole(addr, role)\n\tif !check {\n\t\tt.Errorf(\"%q should has role %q\", addr.String(), role)\n\t}\n}\n\nfunc shouldNotHasRole(t *testing.T, dir *Directory, addr std.Address, role string) {\n\tt.Helper()\n\tcheck := dir.HasRole(addr, role)\n\tif check {\n\t\tt.Errorf(\"%q should not has role %q\", addr.String(), role)\n\t}\n}\n\nfunc shouldHasPerm(t *testing.T, dir *Directory, addr std.Address, verb string, resource string) {\n\tt.Helper()\n\tcheck := dir.HasPerm(addr, verb, resource)\n\tif !check {\n\t\tt.Errorf(\"%q should has perm for %q - %q\", addr.String(), verb, resource)\n\t}\n}\n\nfunc shouldNotHasPerm(t *testing.T, dir *Directory, addr std.Address, verb string, resource string) {\n\tt.Helper()\n\tcheck := dir.HasPerm(addr, verb, resource)\n\tif check {\n\t\tt.Errorf(\"%q should not has perm for %q - %q\", addr.String(), verb, resource)\n\t}\n}\n" + }, + { + "name": "const.gno", + "body": "package acl\n\nconst Everyone string = \"everyone\"\n" + }, + { + "name": "perm.gno", + "body": "package acl\n\nimport \"regexp\"\n\ntype perm struct {\n\tverbs []string\n\tresources []string\n}\n\nfunc (perm perm) hasPerm(verb, resource string) bool {\n\t// check verb\n\tverbOK := false\n\tfor _, pattern := range perm.verbs {\n\t\tif match(pattern, verb) {\n\t\t\tverbOK = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !verbOK {\n\t\treturn false\n\t}\n\n\t// check resource\n\tfor _, pattern := range perm.resources {\n\t\tif match(pattern, resource) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc match(pattern, target string) bool {\n\tif pattern == \".*\" {\n\t\treturn true\n\t}\n\n\tif pattern == target {\n\t\treturn true\n\t}\n\n\t// regexp handling\n\tmatch, _ := regexp.MatchString(pattern, target)\n\treturn match\n}\n" + }, + { + "name": "perms.gno", + "body": "package acl\n\ntype perms []perm\n\nfunc (perms perms) hasPerm(verb, resource string) bool {\n\tfor _, perm := range perms {\n\t\tif perm.hasPerm(verb, resource) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "bf", + "path": "gno.land/p/demo/bf", + "files": [ + { + "name": "bf.gno", + "body": "package bf\n\nimport (\n\t\"strings\"\n)\n\nconst maxlen = 30000\n\nfunc Execute(code string) string {\n\tvar (\n\t\tmemory = make([]byte, maxlen) // memory tape\n\t\tpointer = 0 // initial memory pointer\n\t\tbuf strings.Builder\n\t)\n\n\t// Loop through each character in the code\n\tfor i := 0; i \u003c len(code); i++ {\n\t\tswitch code[i] {\n\t\tcase '\u003e':\n\t\t\t// Increment memory pointer\n\t\t\tpointer++\n\t\t\tif pointer \u003e= maxlen {\n\t\t\t\tpointer = 0\n\t\t\t}\n\t\tcase '\u003c':\n\t\t\t// Decrement memory pointer\n\t\t\tpointer--\n\t\t\tif pointer \u003c 0 {\n\t\t\t\tpointer = maxlen - 1\n\t\t\t}\n\t\tcase '+':\n\t\t\t// Increment the byte at the memory pointer\n\t\t\tmemory[pointer]++\n\t\tcase '-':\n\t\t\t// Decrement the byte at the memory pointer\n\t\t\tmemory[pointer]--\n\t\tcase '.':\n\t\t\t// Output the byte at the memory pointer\n\t\t\tbuf.WriteByte(memory[pointer])\n\t\tcase ',':\n\t\t\t// Input a byte and store it in the memory\n\t\t\tpanic(\"unsupported\")\n\t\t\t// fmt.Scan(\u0026memory[pointer])\n\t\tcase '[':\n\t\t\t// Jump forward past the matching ']' if the byte at the memory pointer is zero\n\t\t\tif memory[pointer] == 0 {\n\t\t\t\tbraceCount := 1\n\t\t\t\tfor braceCount \u003e 0 {\n\t\t\t\t\ti++\n\t\t\t\t\tif code[i] == '[' {\n\t\t\t\t\t\tbraceCount++\n\t\t\t\t\t} else if code[i] == ']' {\n\t\t\t\t\t\tbraceCount--\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase ']':\n\t\t\t// Jump backward to the matching '[' if the byte at the memory pointer is nonzero\n\t\t\tif memory[pointer] != 0 {\n\t\t\t\tbraceCount := 1\n\t\t\t\tfor braceCount \u003e 0 {\n\t\t\t\t\ti--\n\t\t\t\t\tif code[i] == ']' {\n\t\t\t\t\t\tbraceCount++\n\t\t\t\t\t} else if code[i] == '[' {\n\t\t\t\t\t\tbraceCount--\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti-- // Move back one more to compensate for the upcoming increment in the loop\n\t\t\t}\n\t\t}\n\t}\n\treturn buf.String()\n}\n" + }, + { + "name": "bf_test.gno", + "body": "package bf\n\nimport \"testing\"\n\nfunc TestExecuteBrainfuck(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tcode string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"hello\",\n\t\t\tcode: \"++++++++++[\u003e+++++++\u003e++++++++++\u003e+++\u003e+\u003c\u003c\u003c\u003c-]\u003e++.\u003e+.+++++++..+++.\u003e++.\u003c\u003c+++++++++++++++.\u003e.+++.------.--------.\",\n\t\t\texpected: \"Hello World\",\n\t\t},\n\t\t{\n\t\t\tname: \"increment\",\n\t\t\tcode: \"+++++ +++++ [ \u003e +++++ ++ \u003c - ] \u003e +++++ .\",\n\t\t\texpected: \"K\",\n\t\t},\n\t\t// Add more test cases as needed\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult := Execute(tc.code)\n\t\t\tif result != tc.expected {\n\t\t\t\tt.Errorf(\"Expected output: %s, but got: %s\", tc.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "doc.gno", + "body": "// Package bf implements a minimalist Brainfuck virtual machine in Gno.\n//\n// Brainfuck is an esoteric programming language known for its simplicity and minimalistic design.\n// It operates on an array of memory cells, with a memory pointer that can move left or right.\n// The language consists of eight commands: \u003e \u003c + - . , [ ].\n//\n// Usage:\n// To execute Brainfuck code, use the Execute function and provide the code as a string.\n//\n//\tcode := \"++++++++++[\u003e+++++++\u003e++++++++++\u003e+++\u003e+\u003c\u003c\u003c\u003c-]\u003e++.\u003e+.+++++++..+++.\u003e++.\u003c\u003c+++++++++++++++.\u003e.+++.------.--------.\"\n//\toutput := bf.Execute(code)\n//\n// Note:\n// This implementation is a minimalist version and may not handle all edge cases or advanced features of the Brainfuck language.\n//\n// Reference:\n// For more information on Brainfuck, refer to the Wikipedia page: https://en.wikipedia.org/wiki/Brainfuck\npackage bf // import \"gno.land/p/demo/bf\"\n" + }, + { + "name": "run.gno", + "body": "package bf\n\n// for `gno run`\nfunc main() {\n\tcode := \"++++++++++[\u003e+++++++\u003e++++++++++\u003e+++\u003e+\u003c\u003c\u003c\u003c-]\u003e++.\u003e+.+++++++..+++.\u003e++.\u003c\u003c+++++++++++++++.\u003e.+++.------.--------.\"\n\t// TODO: code = os.Args...\n\tExecute(code)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "mux", + "path": "gno.land/p/demo/mux", + "files": [ + { + "name": "doc.gno", + "body": "// Package mux provides a simple routing and rendering library for handling dynamic path-based requests in Gno contracts.\n//\n// The `mux` package aims to offer similar functionality to `http.ServeMux` in Go, but for Gno's Render() requests.\n// It allows you to define routes with dynamic parts and associate them with corresponding handler functions for rendering outputs.\n//\n// Usage:\n// 1. Create a new Router instance using `NewRouter()` to handle routing and rendering logic.\n// 2. Register routes and their associated handler functions using the `Handle(route, handler)` method.\n// 3. Implement the rendering logic within the handler functions, utilizing the `Request` and `ResponseWriter` types.\n// 4. Use the `Render(path)` method to process a given path and execute the corresponding handler function to obtain the rendered output.\n//\n// Route Patterns:\n// Routes can include dynamic parts enclosed in braces, such as \"users/{id}\" or \"hello/{name}\". The `Request` object's `GetVar(key)`\n// method allows you to extract the value of a specific variable from the path based on routing rules.\n//\n// Example:\n//\n//\trouter := mux.NewRouter()\n//\n//\t// Define a route with a variable and associated handler function\n//\trouter.HandleFunc(\"hello/{name}\", func(res *mux.ResponseWriter, req *mux.Request) {\n//\t\tname := req.GetVar(\"name\")\n//\t\tif name != \"\" {\n//\t\t\tres.Write(\"Hello, \" + name + \"!\")\n//\t\t} else {\n//\t\t\tres.Write(\"Hello, world!\")\n//\t\t}\n//\t})\n//\n//\t// Render the output for the \"/hello/Alice\" path\n//\toutput := router.Render(\"hello/Alice\")\n//\t// Output: \"Hello, Alice!\"\n//\n// Note: The `mux` package provides a basic routing and rendering mechanism for simple use cases. For more advanced routing features,\n// consider using more specialized libraries or frameworks.\npackage mux\n" + }, + { + "name": "handler.gno", + "body": "package mux\n\ntype Handler struct {\n\tPattern string\n\tFn HandlerFunc\n}\n\ntype HandlerFunc func(*ResponseWriter, *Request)\n\n// TODO: type ErrHandlerFunc func(*ResponseWriter, *Request) error\n// TODO: NotFoundHandler\n// TODO: AutomaticIndex\n" + }, + { + "name": "helpers.gno", + "body": "package mux\n\nfunc defaultNotFoundHandler(res *ResponseWriter, req *Request) {\n\tres.Write(\"404\")\n}\n" + }, + { + "name": "request.gno", + "body": "package mux\n\nimport \"strings\"\n\n// Request represents an incoming request.\ntype Request struct {\n\tPath string\n\tHandlerPath string\n}\n\n// GetVar retrieves a variable from the path based on routing rules.\nfunc (r *Request) GetVar(key string) string {\n\tvar (\n\t\thandlerParts = strings.Split(r.HandlerPath, \"/\")\n\t\treqParts = strings.Split(r.Path, \"/\")\n\t)\n\n\tfor i := 0; i \u003c len(handlerParts); i++ {\n\t\thandlerPart := handlerParts[i]\n\t\tswitch {\n\t\tcase handlerPart == \"*\":\n\t\t\t// XXX: implement a/b/*/d/e\n\t\t\tpanic(\"not implemented\")\n\t\tcase strings.HasPrefix(handlerPart, \"{\") \u0026\u0026 strings.HasSuffix(handlerPart, \"}\"):\n\t\t\tparameter := handlerPart[1 : len(handlerPart)-1]\n\t\t\tif parameter == key {\n\t\t\t\treturn reqParts[i]\n\t\t\t}\n\t\tdefault:\n\t\t\t// continue\n\t\t}\n\t}\n\n\treturn \"\"\n}\n" + }, + { + "name": "request_test.gno", + "body": "package mux\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestRequest_GetVar(t *testing.T) {\n\tcases := []struct {\n\t\thandlerPath string\n\t\treqPath string\n\t\tgetVarKey string\n\t\texpectedOutput string\n\t}{\n\t\t{\"users/{id}\", \"users/123\", \"id\", \"123\"},\n\t\t{\"users/123\", \"users/123\", \"id\", \"\"},\n\t\t{\"users/{id}\", \"users/123\", \"nonexistent\", \"\"},\n\t\t{\"a/{b}/c/{d}\", \"a/42/c/1337\", \"b\", \"42\"},\n\t\t{\"a/{b}/c/{d}\", \"a/42/c/1337\", \"d\", \"1337\"},\n\t\t{\"{a}\", \"foo\", \"a\", \"foo\"},\n\t\t// TODO: wildcards: a/*/c\n\t\t// TODO: multiple patterns per slashes: a/{b}-{c}/d\n\t}\n\n\tfor _, tt := range cases {\n\t\tname := fmt.Sprintf(\"%s-%s\", tt.handlerPath, tt.reqPath)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\treq := \u0026Request{\n\t\t\t\tHandlerPath: tt.handlerPath,\n\t\t\t\tPath: tt.reqPath,\n\t\t\t}\n\n\t\t\toutput := req.GetVar(tt.getVarKey)\n\t\t\tif output != tt.expectedOutput {\n\t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tt.expectedOutput, output)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "response.gno", + "body": "package mux\n\nimport \"strings\"\n\n// ResponseWriter represents the response writer.\ntype ResponseWriter struct {\n\toutput strings.Builder\n}\n\n// Write appends data to the response output.\nfunc (rw *ResponseWriter) Write(data string) {\n\trw.output.WriteString(data)\n}\n\n// Output returns the final response output.\nfunc (rw *ResponseWriter) Output() string {\n\treturn rw.output.String()\n}\n\n// TODO: func (rw *ResponseWriter) Header()...\n" + }, + { + "name": "router.gno", + "body": "package mux\n\nimport \"strings\"\n\n// Router handles the routing and rendering logic.\ntype Router struct {\n\troutes []Handler\n\tNotFoundHandler HandlerFunc\n}\n\n// NewRouter creates a new Router instance.\nfunc NewRouter() *Router {\n\treturn \u0026Router{\n\t\troutes: make([]Handler, 0),\n\t\tNotFoundHandler: defaultNotFoundHandler,\n\t}\n}\n\n// Render renders the output for the given path using the registered route handler.\nfunc (r *Router) Render(reqPath string) string {\n\treqParts := strings.Split(reqPath, \"/\")\n\n\tfor _, route := range r.routes {\n\t\tpatParts := strings.Split(route.Pattern, \"/\")\n\n\t\tif len(patParts) != len(reqParts) {\n\t\t\tcontinue\n\t\t}\n\n\t\tmatch := true\n\t\tfor i := 0; i \u003c len(patParts); i++ {\n\t\t\tpatPart := patParts[i]\n\t\t\treqPart := reqParts[i]\n\n\t\t\tif patPart == \"*\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif strings.HasPrefix(patPart, \"{\") \u0026\u0026 strings.HasSuffix(patPart, \"}\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif patPart != reqPart {\n\t\t\t\tmatch = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif match {\n\t\t\treq := \u0026Request{\n\t\t\t\tPath: reqPath,\n\t\t\t\tHandlerPath: route.Pattern,\n\t\t\t}\n\t\t\tres := \u0026ResponseWriter{}\n\t\t\troute.Fn(res, req)\n\t\t\treturn res.Output()\n\t\t}\n\t}\n\n\t// not found\n\treq := \u0026Request{Path: reqPath}\n\tres := \u0026ResponseWriter{}\n\tr.NotFoundHandler(res, req)\n\treturn res.Output()\n}\n\n// Handle registers a route and its handler function.\nfunc (r *Router) HandleFunc(pattern string, fn HandlerFunc) {\n\troute := Handler{Pattern: pattern, Fn: fn}\n\tr.routes = append(r.routes, route)\n}\n" + }, + { + "name": "router_test.gno", + "body": "package mux\n\nimport \"testing\"\n\nfunc TestRouter_Render(t *testing.T) {\n\t// Define handlers and route configuration\n\trouter := NewRouter()\n\trouter.HandleFunc(\"hello/{name}\", func(res *ResponseWriter, req *Request) {\n\t\tname := req.GetVar(\"name\")\n\t\tif name != \"\" {\n\t\t\tres.Write(\"Hello, \" + name + \"!\")\n\t\t} else {\n\t\t\tres.Write(\"Hello, world!\")\n\t\t}\n\t})\n\trouter.HandleFunc(\"hi\", func(res *ResponseWriter, req *Request) {\n\t\tres.Write(\"Hi, earth!\")\n\t})\n\n\tcases := []struct {\n\t\tpath string\n\t\texpectedOutput string\n\t}{\n\t\t{\"hello/Alice\", \"Hello, Alice!\"},\n\t\t{\"hi\", \"Hi, earth!\"},\n\t\t{\"hello/Bob\", \"Hello, Bob!\"},\n\t\t// TODO: {\"hello\", \"Hello, world!\"},\n\t\t// TODO: hello/, /hello, hello//Alice, hello/Alice/, hello/Alice/Bob, etc\n\t}\n\tfor _, tt := range cases {\n\t\tt.Run(tt.path, func(t *testing.T) {\n\t\t\toutput := router.Render(tt.path)\n\t\t\tif output != tt.expectedOutput {\n\t\t\t\tt.Errorf(\"Expected output %q, but got %q\", tt.expectedOutput, output)\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ufmt", + "path": "gno.land/p/demo/ufmt", + "files": [ + { + "name": "ufmt.gno", + "body": "// Package ufmt provides utility functions for formatting strings, similarly\n// to the Go package \"fmt\", of which only a subset is currently supported\n// (hence the name µfmt - micro fmt).\npackage ufmt\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Println formats using the default formats for its operands and writes to standard output.\n// Println writes the given arguments to standard output with spaces between arguments\n// and a newline at the end.\nfunc Println(args ...interface{}) {\n\tvar strs []string\n\tfor _, arg := range args {\n\t\tswitch v := arg.(type) {\n\t\tcase string:\n\t\t\tstrs = append(strs, v)\n\t\tcase int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:\n\t\t\tstrs = append(strs, Sprintf(\"%d\", v))\n\t\tcase bool:\n\t\t\tif v {\n\t\t\t\tstrs = append(strs, \"true\")\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tstrs = append(strs, \"false\")\n\t\tdefault:\n\t\t\tstrs = append(strs, \"(unhandled)\")\n\t\t}\n\t}\n\n\t// TODO: remove println after gno supports os.Stdout\n\tprintln(strings.Join(strs, \" \"))\n}\n\n// Sprintf offers similar functionality to Go's fmt.Sprintf, or the sprintf\n// equivalent available in many languages, including C/C++.\n// The number of args passed must exactly match the arguments consumed by the format.\n// A limited number of formatting verbs and features are currently supported,\n// hence the name ufmt (µfmt, micro-fmt).\n//\n// The currently formatted verbs are the following:\n//\n//\t%s: places a string value directly.\n//\t If the value implements the interface interface{ String() string },\n//\t the String() method is called to retrieve the value.\n//\t%d: formats an integer value using package \"strconv\".\n//\t Currently supports only uint, uint64, int, int64.\n//\t%t: formats a boolean value to \"true\" or \"false\".\n//\t%%: outputs a literal %. Does not consume an argument.\nfunc Sprintf(format string, args ...interface{}) string {\n\t// we use runes to handle multi-byte characters\n\tsTor := []rune(format)\n\tend := len(sTor)\n\targNum := 0\n\targLen := len(args)\n\tbuf := \"\"\n\n\tfor i := 0; i \u003c end; {\n\t\tisLast := i == end-1\n\t\tc := string(sTor[i])\n\n\t\tif isLast || c != \"%\" {\n\t\t\t// we don't check for invalid format like a one ending with \"%\"\n\t\t\tbuf += string(c)\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\n\t\tverb := string(sTor[i+1])\n\t\tif verb == \"%\" {\n\t\t\tbuf += \"%\"\n\t\t\ti += 2\n\t\t\tcontinue\n\t\t}\n\n\t\tif argNum \u003e argLen {\n\t\t\tpanic(\"invalid number of arguments to ufmt.Sprintf\")\n\t\t}\n\t\targ := args[argNum]\n\t\targNum++\n\n\t\tswitch verb {\n\t\tcase \"s\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase interface{ String() string }:\n\t\t\t\tbuf += v.String()\n\t\t\tcase string:\n\t\t\t\tbuf += v\n\t\t\tdefault:\n\t\t\t\tbuf += \"(unhandled)\"\n\t\t\t}\n\t\tcase \"d\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase int:\n\t\t\t\tbuf += strconv.Itoa(v)\n\t\t\tcase int8:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase int16:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase int32:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase int64:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase uint:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint8:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint16:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint32:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint64:\n\t\t\t\tbuf += strconv.FormatUint(v, 10)\n\t\t\tdefault:\n\t\t\t\tbuf += \"(unhandled)\"\n\t\t\t}\n\t\tcase \"t\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase bool:\n\t\t\t\tif v {\n\t\t\t\t\tbuf += \"true\"\n\t\t\t\t} else {\n\t\t\t\t\tbuf += \"false\"\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbuf += \"(unhandled)\"\n\t\t\t}\n\t\t// % handled before, as it does not consume an argument\n\t\tdefault:\n\t\t\tbuf += \"(unhandled)\"\n\t\t}\n\n\t\ti += 2\n\t}\n\tif argNum \u003c argLen {\n\t\tpanic(\"too many arguments to ufmt.Sprintf\")\n\t}\n\treturn buf\n}\n\n// errMsg implements the error interface.\ntype errMsg struct {\n\tmsg string\n}\n\n// Error defines the requirements of the error interface.\n// It functions similarly to Go's errors.New()\nfunc (e *errMsg) Error() string {\n\treturn e.msg\n}\n\n// Errorf is a function that mirrors the functionality of fmt.Errorf.\n//\n// It takes a format string and arguments to create a formatted string,\n// then sets this string as the 'msg' field of an errMsg struct and returns a pointer to this struct.\n//\n// This function operates in a similar manner to Go's fmt.Errorf,\n// providing a way to create formatted error messages.\n//\n// The currently formatted verbs are the following:\n//\n//\t%s: places a string value directly.\n//\t If the value implements the interface interface{ String() string },\n//\t the String() method is called to retrieve the value.\n//\t%d: formats an integer value using package \"strconv\".\n//\t Currently supports only uint, uint64, int, int64.\n//\t%t: formats a boolean value to \"true\" or \"false\".\n//\t%%: outputs a literal %. Does not consume an argument.\nfunc Errorf(format string, args ...interface{}) error {\n\treturn \u0026errMsg{Sprintf(format, args...)}\n}\n" + }, + { + "name": "ufmt_test.gno", + "body": "package ufmt\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype stringer struct{}\n\nfunc (stringer) String() string {\n\treturn \"I'm a stringer\"\n}\n\nfunc TestSprintf(t *testing.T) {\n\tcases := []struct {\n\t\tformat string\n\t\tvalues []interface{}\n\t\texpectedOutput string\n\t}{\n\t\t{\"hello %s!\", []interface{}{\"planet\"}, \"hello planet!\"},\n\t\t{\"hi %%%s!\", []interface{}{\"worl%d\"}, \"hi %worl%d!\"},\n\t\t{\"string [%s]\", []interface{}{\"foo\"}, \"string [foo]\"},\n\t\t{\"int [%d]\", []interface{}{int(42)}, \"int [42]\"},\n\t\t{\"int8 [%d]\", []interface{}{int8(8)}, \"int8 [8]\"},\n\t\t{\"int16 [%d]\", []interface{}{int16(16)}, \"int16 [16]\"},\n\t\t{\"int32 [%d]\", []interface{}{int32(32)}, \"int32 [32]\"},\n\t\t{\"int64 [%d]\", []interface{}{int64(64)}, \"int64 [64]\"},\n\t\t{\"uint [%d]\", []interface{}{uint(42)}, \"uint [42]\"},\n\t\t{\"uint8 [%d]\", []interface{}{uint8(8)}, \"uint8 [8]\"},\n\t\t{\"uint16 [%d]\", []interface{}{uint16(16)}, \"uint16 [16]\"},\n\t\t{\"uint32 [%d]\", []interface{}{uint32(32)}, \"uint32 [32]\"},\n\t\t{\"uint64 [%d]\", []interface{}{uint64(64)}, \"uint64 [64]\"},\n\t\t{\"bool [%t]\", []interface{}{true}, \"bool [true]\"},\n\t\t{\"bool [%t]\", []interface{}{false}, \"bool [false]\"},\n\t\t{\"invalid bool [%t]\", []interface{}{\"invalid\"}, \"invalid bool [(unhandled)]\"},\n\t\t{\"invalid integer [%d]\", []interface{}{\"invalid\"}, \"invalid integer [(unhandled)]\"},\n\t\t{\"invalid string [%s]\", []interface{}{1}, \"invalid string [(unhandled)]\"},\n\t\t{\"no args\", nil, \"no args\"},\n\t\t{\"finish with %\", nil, \"finish with %\"},\n\t\t{\"stringer [%s]\", []interface{}{stringer{}}, \"stringer [I'm a stringer]\"},\n\t\t{\"â\", nil, \"â\"},\n\t\t{\"Hello, World! 😊\", nil, \"Hello, World! 😊\"},\n\t\t{\"unicode formatting: %s\", []interface{}{\"😊\"}, \"unicode formatting: 😊\"},\n\t}\n\n\tfor _, tc := range cases {\n\t\tname := fmt.Sprintf(tc.format, tc.values...)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Sprintf(tc.format, tc.values...)\n\t\t\tif got != tc.expectedOutput {\n\t\t\t\tt.Errorf(\"got %q, want %q.\", got, tc.expectedOutput)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestErrorf(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tformat string\n\t\targs []interface{}\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"simple string\",\n\t\t\tformat: \"error: %s\",\n\t\t\targs: []interface{}{\"something went wrong\"},\n\t\t\texpected: \"error: something went wrong\",\n\t\t},\n\t\t{\n\t\t\tname: \"integer value\",\n\t\t\tformat: \"value: %d\",\n\t\t\targs: []interface{}{42},\n\t\t\texpected: \"value: 42\",\n\t\t},\n\t\t{\n\t\t\tname: \"boolean value\",\n\t\t\tformat: \"success: %t\",\n\t\t\targs: []interface{}{true},\n\t\t\texpected: \"success: true\",\n\t\t},\n\t\t{\n\t\t\tname: \"multiple values\",\n\t\t\tformat: \"error %d: %s (success=%t)\",\n\t\t\targs: []interface{}{123, \"failure occurred\", false},\n\t\t\texpected: \"error 123: failure occurred (success=false)\",\n\t\t},\n\t\t{\n\t\t\tname: \"literal percent\",\n\t\t\tformat: \"literal %%\",\n\t\t\targs: []interface{}{},\n\t\t\texpected: \"literal %\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := Errorf(tt.format, tt.args...)\n\t\t\tif err.Error() != tt.expected {\n\t\t\t\tt.Errorf(\"Errorf(%q, %v) = %q, expected %q\", tt.format, tt.args, err.Error(), tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// NOTE: Currently, there is no way to get the output of Println without using os.Stdout,\n// so we can only test that it doesn't panic and print arguments well.\nfunc TestPrintln(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs []interface{}\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Empty args\",\n\t\t\targs: []interface{}{},\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"String args\",\n\t\t\targs: []interface{}{\"Hello\", \"World\"},\n\t\t\texpected: \"Hello World\",\n\t\t},\n\t\t{\n\t\t\tname: \"Integer args\",\n\t\t\targs: []interface{}{1, 2, 3},\n\t\t\texpected: \"1 2 3\",\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed args\",\n\t\t\targs: []interface{}{\"Hello\", 42, true, false, \"World\"},\n\t\t\texpected: \"Hello 42 true false World\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unhandled type\",\n\t\t\targs: []interface{}{\"Hello\", 3.14, []int{1, 2, 3}},\n\t\t\texpected: \"Hello (unhandled) (unhandled)\",\n\t\t},\n\t}\n\n\t// TODO: replace os.Stdout with a buffer to capture the output and test it.\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tPrintln(tt.args...)\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "blog", + "path": "gno.land/p/demo/blog", + "files": [ + { + "name": "blog.gno", + "body": "package blog\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Blog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPosts avl.Tree // slug -\u003e *Post\n\tPostsPublished avl.Tree // published-date -\u003e *Post\n\tPostsAlphabetical avl.Tree // title -\u003e *Post\n\tNoBreadcrumb bool\n}\n\nfunc (b Blog) RenderLastPostsWidget(limit int) string {\n\tif b.PostsPublished.Size() == 0 {\n\t\treturn \"No posts.\"\n\t}\n\n\toutput := \"\"\n\ti := 0\n\tb.PostsPublished.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tp := value.(*Post)\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", p.Title, p.URL())\n\t\ti++\n\t\treturn i \u003e= limit\n\t})\n\treturn output\n}\n\nfunc (b Blog) RenderHome(res *mux.ResponseWriter, req *mux.Request) {\n\tif !b.NoBreadcrumb {\n\t\tres.Write(breadcrumb([]string{b.Title}))\n\t}\n\n\tif b.Posts.Size() == 0 {\n\t\tres.Write(\"No posts.\")\n\t\treturn\n\t}\n\n\tres.Write(\"\u003cdiv class='columns-3'\u003e\")\n\tb.PostsPublished.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\t\tres.Write(post.RenderListItem())\n\t\treturn false\n\t})\n\tres.Write(\"\u003c/div\u003e\")\n\n\t// FIXME: tag list/cloud.\n}\n\nfunc (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) {\n\tslug := req.GetVar(\"slug\")\n\n\tpost, found := b.Posts.Get(slug)\n\tif !found {\n\t\tres.Write(\"404\")\n\t\treturn\n\t}\n\tp := post.(*Post)\n\n\tres.Write(\"\u003cmain class='gno-tmpl-page'\u003e\" + \"\\n\\n\")\n\n\tres.Write(\"# \" + p.Title + \"\\n\\n\")\n\tres.Write(p.Body + \"\\n\\n\")\n\tres.Write(\"---\\n\\n\")\n\n\tres.Write(p.RenderTagList() + \"\\n\\n\")\n\tres.Write(p.RenderAuthorList() + \"\\n\\n\")\n\tres.Write(p.RenderPublishData() + \"\\n\\n\")\n\n\tres.Write(\"---\\n\")\n\tres.Write(\"\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\\n\\n\")\n\n\t// comments\n\tp.Comments.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcomment := value.(*Comment)\n\t\tres.Write(comment.RenderListItem())\n\t\treturn false\n\t})\n\n\tres.Write(\"\u003c/details\u003e\\n\")\n\tres.Write(\"\u003c/main\u003e\")\n}\n\nfunc (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) {\n\tslug := req.GetVar(\"slug\")\n\n\tif slug == \"\" {\n\t\tres.Write(\"404\")\n\t\treturn\n\t}\n\n\tif !b.NoBreadcrumb {\n\t\tbreadStr := breadcrumb([]string{\n\t\t\tufmt.Sprintf(\"[%s](%s)\", b.Title, b.Prefix),\n\t\t\t\"t\",\n\t\t\tslug,\n\t\t})\n\t\tres.Write(breadStr)\n\t}\n\n\tnb := 0\n\tb.Posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\t\tif !post.HasTag(slug) {\n\t\t\treturn false\n\t\t}\n\t\tres.Write(post.RenderListItem())\n\t\tnb++\n\t\treturn false\n\t})\n\tif nb == 0 {\n\t\tres.Write(\"No posts.\")\n\t}\n}\n\nfunc (b Blog) Render(path string) string {\n\trouter := mux.NewRouter()\n\trouter.HandleFunc(\"\", b.RenderHome)\n\trouter.HandleFunc(\"p/{slug}\", b.RenderPost)\n\trouter.HandleFunc(\"t/{slug}\", b.RenderTag)\n\treturn router.Render(path)\n}\n\nfunc (b *Blog) NewPost(publisher std.Address, slug, title, body, pubDate string, authors, tags []string) error {\n\tif _, found := b.Posts.Get(slug); found {\n\t\treturn ErrPostSlugExists\n\t}\n\n\tvar parsedTime time.Time\n\tvar err error\n\tif pubDate != \"\" {\n\t\tparsedTime, err = time.Parse(time.RFC3339, pubDate)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// If no publication date was passed in by caller, take current block time\n\t\tparsedTime = time.Now()\n\t}\n\n\tpost := \u0026Post{\n\t\tPublisher: publisher,\n\t\tAuthors: authors,\n\t\tSlug: slug,\n\t\tTitle: title,\n\t\tBody: body,\n\t\tTags: tags,\n\t\tCreatedAt: parsedTime,\n\t}\n\n\treturn b.prepareAndSetPost(post, false)\n}\n\nfunc (b *Blog) prepareAndSetPost(post *Post, edit bool) error {\n\tpost.Title = strings.TrimSpace(post.Title)\n\tpost.Body = strings.TrimSpace(post.Body)\n\n\tif post.Title == \"\" {\n\t\treturn ErrPostTitleMissing\n\t}\n\tif post.Body == \"\" {\n\t\treturn ErrPostBodyMissing\n\t}\n\tif post.Slug == \"\" {\n\t\treturn ErrPostSlugMissing\n\t}\n\n\tpost.Blog = b\n\tpost.UpdatedAt = time.Now()\n\n\ttrimmedTitleKey := getTitleKey(post.Title)\n\tpubDateKey := getPublishedKey(post.CreatedAt)\n\n\tif !edit {\n\t\t// Cannot have two posts with same title key\n\t\tif _, found := b.PostsAlphabetical.Get(trimmedTitleKey); found {\n\t\t\treturn ErrPostTitleExists\n\t\t}\n\t\t// Cannot have two posts with *exact* same timestamp\n\t\tif _, found := b.PostsPublished.Get(pubDateKey); found {\n\t\t\treturn ErrPostPubDateExists\n\t\t}\n\t}\n\n\t// Store post under keys\n\tb.PostsAlphabetical.Set(trimmedTitleKey, post)\n\tb.PostsPublished.Set(pubDateKey, post)\n\tb.Posts.Set(post.Slug, post)\n\n\treturn nil\n}\n\nfunc (b *Blog) RemovePost(slug string) {\n\tp, exists := b.Posts.Get(slug)\n\tif !exists {\n\t\tpanic(\"post with specified slug doesn't exist\")\n\t}\n\n\tpost := p.(*Post)\n\n\ttitleKey := getTitleKey(post.Title)\n\tpublishedKey := getPublishedKey(post.CreatedAt)\n\n\t_, _ = b.Posts.Remove(slug)\n\t_, _ = b.PostsAlphabetical.Remove(titleKey)\n\t_, _ = b.PostsPublished.Remove(publishedKey)\n}\n\nfunc (b *Blog) GetPost(slug string) *Post {\n\tpost, found := b.Posts.Get(slug)\n\tif !found {\n\t\treturn nil\n\t}\n\treturn post.(*Post)\n}\n\ntype Post struct {\n\tBlog *Blog\n\tSlug string // FIXME: save space?\n\tTitle string\n\tBody string\n\tCreatedAt time.Time\n\tUpdatedAt time.Time\n\tComments avl.Tree\n\tAuthors []string\n\tPublisher std.Address\n\tTags []string\n\tCommentIndex int\n}\n\nfunc (p *Post) Update(title, body, publicationDate string, authors, tags []string) error {\n\tp.Title = title\n\tp.Body = body\n\tp.Tags = tags\n\tp.Authors = authors\n\n\tparsedTime, err := time.Parse(time.RFC3339, publicationDate)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.CreatedAt = parsedTime\n\treturn p.Blog.prepareAndSetPost(p, true)\n}\n\nfunc (p *Post) AddComment(author std.Address, comment string) error {\n\tif p == nil {\n\t\treturn ErrNoSuchPost\n\t}\n\tp.CommentIndex++\n\tcommentKey := strconv.Itoa(p.CommentIndex)\n\tcomment = strings.TrimSpace(comment)\n\tp.Comments.Set(commentKey, \u0026Comment{\n\t\tPost: p,\n\t\tCreatedAt: time.Now(),\n\t\tAuthor: author,\n\t\tComment: comment,\n\t})\n\n\treturn nil\n}\n\nfunc (p *Post) DeleteComment(index int) error {\n\tif p == nil {\n\t\treturn ErrNoSuchPost\n\t}\n\tcommentKey := strconv.Itoa(index)\n\tp.Comments.Remove(commentKey)\n\treturn nil\n}\n\nfunc (p *Post) HasTag(tag string) bool {\n\tif p == nil {\n\t\treturn false\n\t}\n\tfor _, t := range p.Tags {\n\t\tif t == tag {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (p *Post) RenderListItem() string {\n\tif p == nil {\n\t\treturn \"error: no such post\\n\"\n\t}\n\toutput := \"\u003cdiv\u003e\\n\\n\"\n\toutput += ufmt.Sprintf(\"### [%s](%s)\\n\", p.Title, p.URL())\n\t// output += ufmt.Sprintf(\"**[Learn More](%s)**\\n\\n\", p.URL())\n\n\toutput += \" \" + p.CreatedAt.Format(\"02 Jan 2006\")\n\t// output += p.Summary() + \"\\n\\n\"\n\t// output += p.RenderTagList() + \"\\n\\n\"\n\toutput += \"\\n\"\n\toutput += \"\u003c/div\u003e\"\n\treturn output\n}\n\n// Render post tags\nfunc (p *Post) RenderTagList() string {\n\tif p == nil {\n\t\treturn \"error: no such post\\n\"\n\t}\n\tif len(p.Tags) == 0 {\n\t\treturn \"\"\n\t}\n\n\toutput := \"Tags: \"\n\tfor idx, tag := range p.Tags {\n\t\tif idx \u003e 0 {\n\t\t\toutput += \" \"\n\t\t}\n\t\ttagURL := p.Blog.Prefix + \"t/\" + tag\n\t\toutput += ufmt.Sprintf(\"[#%s](%s)\", tag, tagURL)\n\n\t}\n\treturn output\n}\n\n// Render authors if there are any\nfunc (p *Post) RenderAuthorList() string {\n\tout := \"Written\"\n\tif len(p.Authors) != 0 {\n\t\tout += \" by \"\n\n\t\tfor idx, author := range p.Authors {\n\t\t\tout += author\n\t\t\tif idx \u003c len(p.Authors)-1 {\n\t\t\t\tout += \", \"\n\t\t\t}\n\t\t}\n\t}\n\tout += \" on \" + p.CreatedAt.Format(\"02 Jan 2006\")\n\n\treturn out\n}\n\nfunc (p *Post) RenderPublishData() string {\n\tout := \"Published \"\n\tif p.Publisher != \"\" {\n\t\tout += \"by \" + p.Publisher.String() + \" \"\n\t}\n\tout += \"to \" + p.Blog.Title\n\n\treturn out\n}\n\nfunc (p *Post) URL() string {\n\tif p == nil {\n\t\treturn p.Blog.Prefix + \"404\"\n\t}\n\treturn p.Blog.Prefix + \"p/\" + p.Slug\n}\n\nfunc (p *Post) Summary() string {\n\tif p == nil {\n\t\treturn \"error: no such post\\n\"\n\t}\n\n\t// FIXME: better summary.\n\tlines := strings.Split(p.Body, \"\\n\")\n\tif len(lines) \u003c= 3 {\n\t\treturn p.Body\n\t}\n\treturn strings.Join(lines[0:3], \"\\n\") + \"...\"\n}\n\ntype Comment struct {\n\tPost *Post\n\tCreatedAt time.Time\n\tAuthor std.Address\n\tComment string\n}\n\nfunc (c Comment) RenderListItem() string {\n\toutput := \"\u003ch5\u003e\"\n\toutput += c.Comment + \"\\n\\n\"\n\toutput += \"\u003c/h5\u003e\"\n\n\toutput += \"\u003ch6\u003e\"\n\toutput += ufmt.Sprintf(\"by %s on %s\", c.Author, c.CreatedAt.Format(time.RFC822))\n\toutput += \"\u003c/h6\u003e\\n\\n\"\n\n\toutput += \"---\\n\\n\"\n\n\treturn output\n}\n" + }, + { + "name": "blog_test.gno", + "body": "package blog\n\n// TODO: add generic tests here.\n// right now, you can checkout r/gnoland/blog/*_test.gno.\n" + }, + { + "name": "errors.gno", + "body": "package blog\n\nimport \"errors\"\n\nvar (\n\tErrPostTitleMissing = errors.New(\"post title is missing\")\n\tErrPostSlugMissing = errors.New(\"post slug is missing\")\n\tErrPostBodyMissing = errors.New(\"post body is missing\")\n\tErrPostSlugExists = errors.New(\"post with specified slug already exists\")\n\tErrPostPubDateExists = errors.New(\"post with specified publication date exists\")\n\tErrPostTitleExists = errors.New(\"post with specified title already exists\")\n\tErrNoSuchPost = errors.New(\"no such post\")\n)\n" + }, + { + "name": "util.gno", + "body": "package blog\n\nimport (\n\t\"strings\"\n\t\"time\"\n)\n\nfunc breadcrumb(parts []string) string {\n\treturn \"# \" + strings.Join(parts, \" / \") + \"\\n\\n\"\n}\n\nfunc getTitleKey(title string) string {\n\treturn strings.Replace(title, \" \", \"\", -1)\n}\n\nfunc getPublishedKey(t time.Time) string {\n\treturn t.Format(time.RFC3339)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "cford32", + "path": "gno.land/p/demo/cford32", + "files": [ + { + "name": "LICENSE", + "body": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, + { + "name": "README.md", + "body": "# cford32\n\n```\npackage cford32 // import \"gno.land/p/demo/cford32\"\n\nPackage cford32 implements a base32-like encoding/decoding package, with the\nencoding scheme specified by Douglas Crockford.\n\nFrom the website, the requirements of said encoding scheme are to:\n\n - Be human readable and machine readable.\n - Be compact. Humans have difficulty in manipulating long strings of arbitrary\n symbols.\n - Be error resistant. Entering the symbols must not require keyboarding\n gymnastics.\n - Be pronounceable. Humans should be able to accurately transmit the symbols\n to other humans using a telephone.\n\nThis is slightly different from a simple difference in encoding table from\nthe Go's stdlib `encoding/base32`, as when decoding the characters i I l L are\nparsed as 1, and o O is parsed as 0.\n\nThis package additionally provides ways to encode uint64's efficiently, as well\nas efficient encoding to a lowercase variation of the encoding. The encodings\nnever use paddings.\n\n# Uint64 Encoding\n\nAside from lower/uppercase encoding, there is a compact encoding, allowing to\nencode all values in [0,2^34), and the full encoding, allowing all values in\n[0,2^64). The compact encoding uses 7 characters, and the full encoding uses 13\ncharacters. Both are parsed unambiguously by the Uint64 decoder.\n\nThe compact encodings have the first character between ['0','f'], while the\nfull encoding's first character ranges between ['g','z']. Practically, in your\nusage of the package, you should consider which one to use and stick with it,\nwhile considering that the compact encoding, once it reaches 2^34, automatically\nswitches to the full encoding. The properties of the generated strings are still\nmaintained: for instance, any two encoded uint64s x,y consistently generated\nwith the compact encoding, if the numeric value is x \u003c y, will also be x \u003c y in\nlexical ordering. However, values [0,2^34) have a \"double encoding\", which if\nmixed together lose the lexical ordering property.\n\nThe Uint64 encoding is most useful for generating string versions of Uint64 IDs.\nPractically, it allows you to retain sleek and compact IDs for your application\nfor the first 2^34 (\u003e17 billion) entities, while seamlessly rolling over to the\nfull encoding should you exceed that. You are encouraged to use it unless you\nhave a requirement or preferences for IDs consistently being always the same\nsize.\n\nTo use the cford32 encoding for IDs, you may want to consider using package\ngno.land/p/demo/seqid.\n\n[specified by Douglas Crockford]: https://www.crockford.com/base32.html\n\nfunc AppendCompact(id uint64, b []byte) []byte\nfunc AppendDecode(dst, src []byte) ([]byte, error)\nfunc AppendEncode(dst, src []byte) []byte\nfunc AppendEncodeLower(dst, src []byte) []byte\nfunc Decode(dst, src []byte) (n int, err error)\nfunc DecodeString(s string) ([]byte, error)\nfunc DecodedLen(n int) int\nfunc Encode(dst, src []byte)\nfunc EncodeLower(dst, src []byte)\nfunc EncodeToString(src []byte) string\nfunc EncodeToStringLower(src []byte) string\nfunc EncodedLen(n int) int\nfunc NewDecoder(r io.Reader) io.Reader\nfunc NewEncoder(w io.Writer) io.WriteCloser\nfunc NewEncoderLower(w io.Writer) io.WriteCloser\nfunc PutCompact(id uint64) []byte\nfunc PutUint64(id uint64) [13]byte\nfunc PutUint64Lower(id uint64) [13]byte\nfunc Uint64(b []byte) (uint64, error)\ntype CorruptInputError int64\n```\n" + }, + { + "name": "cford32.gno", + "body": "// Modified from the Go Source code for encoding/base32.\n// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package cford32 implements a base32-like encoding/decoding package, with the\n// encoding scheme [specified by Douglas Crockford].\n//\n// From the website, the requirements of said encoding scheme are to:\n//\n// - Be human readable and machine readable.\n// - Be compact. Humans have difficulty in manipulating long strings of arbitrary symbols.\n// - Be error resistant. Entering the symbols must not require keyboarding gymnastics.\n// - Be pronounceable. Humans should be able to accurately transmit the symbols to other humans using a telephone.\n//\n// This is slightly different from a simple difference in encoding table from\n// the Go's stdlib `encoding/base32`, as when decoding the characters i I l L are\n// parsed as 1, and o O is parsed as 0.\n//\n// This package additionally provides ways to encode uint64's efficiently,\n// as well as efficient encoding to a lowercase variation of the encoding.\n// The encodings never use paddings.\n//\n// # Uint64 Encoding\n//\n// Aside from lower/uppercase encoding, there is a compact encoding, allowing\n// to encode all values in [0,2^34), and the full encoding, allowing all\n// values in [0,2^64). The compact encoding uses 7 characters, and the full\n// encoding uses 13 characters. Both are parsed unambiguously by the Uint64\n// decoder.\n//\n// The compact encodings have the first character between ['0','f'], while the\n// full encoding's first character ranges between ['g','z']. Practically, in\n// your usage of the package, you should consider which one to use and stick\n// with it, while considering that the compact encoding, once it reaches 2^34,\n// automatically switches to the full encoding. The properties of the generated\n// strings are still maintained: for instance, any two encoded uint64s x,y\n// consistently generated with the compact encoding, if the numeric value is\n// x \u003c y, will also be x \u003c y in lexical ordering. However, values [0,2^34) have a\n// \"double encoding\", which if mixed together lose the lexical ordering property.\n//\n// The Uint64 encoding is most useful for generating string versions of Uint64\n// IDs. Practically, it allows you to retain sleek and compact IDs for your\n// application for the first 2^34 (\u003e17 billion) entities, while seamlessly\n// rolling over to the full encoding should you exceed that. You are encouraged\n// to use it unless you have a requirement or preferences for IDs consistently\n// being always the same size.\n//\n// To use the cford32 encoding for IDs, you may want to consider using package\n// [gno.land/p/demo/seqid].\n//\n// [specified by Douglas Crockford]: https://www.crockford.com/base32.html\npackage cford32\n\nimport (\n\t\"io\"\n\t\"strconv\"\n)\n\nconst (\n\tencTable = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\"\n\tencTableLower = \"0123456789abcdefghjkmnpqrstvwxyz\"\n\n\t// each line is 16 bytes\n\tdecTable = \"\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" + // 00-0f\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" + // 10-1f\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" + // 20-2f\n\t\t\"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\xff\\xff\\xff\\xff\\xff\\xff\" + // 30-3f\n\t\t\"\\xff\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\\x10\\x11\\x01\\x12\\x13\\x01\\x14\\x15\\x00\" + // 40-4f\n\t\t\"\\x16\\x17\\x18\\x19\\x1a\\xff\\x1b\\x1c\\x1d\\x1e\\x1f\\xff\\xff\\xff\\xff\\xff\" + // 50-5f\n\t\t\"\\xff\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\\x10\\x11\\x01\\x12\\x13\\x01\\x14\\x15\\x00\" + // 60-6f\n\t\t\"\\x16\\x17\\x18\\x19\\x1a\\xff\\x1b\\x1c\\x1d\\x1e\\x1f\\xff\\xff\\xff\\xff\\xff\" + // 70-7f\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" + // 80-ff (not ASCII)\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\" +\n\t\t\"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n)\n\n// CorruptInputError is returned by parsing functions when an invalid character\n// in the input is found. The integer value represents the byte index where\n// the error occurred.\n//\n// This is typically because the given character does not exist in the encoding.\ntype CorruptInputError int64\n\nfunc (e CorruptInputError) Error() string {\n\treturn \"illegal cford32 data at input byte \" + strconv.FormatInt(int64(e), 10)\n}\n\n// Uint64 parses a cford32-encoded byte slice into a uint64.\n//\n// - The parser requires all provided character to be valid cford32 characters.\n// - The parser disregards case.\n// - If the first character is '0' \u003c= c \u003c= 'f', then the passed value is assumed\n// encoded in the compact encoding, and must be 7 characters long.\n// - If the first character is 'g' \u003c= c \u003c= 'z', then the passed value is\n// assumed encoded in the full encoding, and must be 13 characters long.\n//\n// If any of these requirements fail, a CorruptInputError will be returned.\nfunc Uint64(b []byte) (uint64, error) {\n\tswitch {\n\tdefault:\n\t\treturn 0, CorruptInputError(0)\n\tcase len(b) == 7 \u0026\u0026 b[0] \u003e= '0' \u0026\u0026 b[0] \u003c= 'f':\n\t\tdecVals := [7]byte{\n\t\t\tdecTable[b[0]],\n\t\t\tdecTable[b[1]],\n\t\t\tdecTable[b[2]],\n\t\t\tdecTable[b[3]],\n\t\t\tdecTable[b[4]],\n\t\t\tdecTable[b[5]],\n\t\t\tdecTable[b[6]],\n\t\t}\n\t\tfor idx, v := range decVals {\n\t\t\tif v \u003e= 32 {\n\t\t\t\treturn 0, CorruptInputError(idx)\n\t\t\t}\n\t\t}\n\n\t\treturn 0 +\n\t\t\tuint64(decVals[0])\u003c\u003c30 |\n\t\t\tuint64(decVals[1])\u003c\u003c25 |\n\t\t\tuint64(decVals[2])\u003c\u003c20 |\n\t\t\tuint64(decVals[3])\u003c\u003c15 |\n\t\t\tuint64(decVals[4])\u003c\u003c10 |\n\t\t\tuint64(decVals[5])\u003c\u003c5 |\n\t\t\tuint64(decVals[6]), nil\n\tcase len(b) == 13 \u0026\u0026 b[0] \u003e= 'g' \u0026\u0026 b[0] \u003c= 'z':\n\t\tdecVals := [13]byte{\n\t\t\tdecTable[b[0]] \u0026 0x0F, // disregard high bit\n\t\t\tdecTable[b[1]],\n\t\t\tdecTable[b[2]],\n\t\t\tdecTable[b[3]],\n\t\t\tdecTable[b[4]],\n\t\t\tdecTable[b[5]],\n\t\t\tdecTable[b[6]],\n\t\t\tdecTable[b[7]],\n\t\t\tdecTable[b[8]],\n\t\t\tdecTable[b[9]],\n\t\t\tdecTable[b[10]],\n\t\t\tdecTable[b[11]],\n\t\t\tdecTable[b[12]],\n\t\t}\n\t\tfor idx, v := range decVals {\n\t\t\tif v \u003e= 32 {\n\t\t\t\treturn 0, CorruptInputError(idx)\n\t\t\t}\n\t\t}\n\n\t\treturn 0 +\n\t\t\tuint64(decVals[0])\u003c\u003c60 |\n\t\t\tuint64(decVals[1])\u003c\u003c55 |\n\t\t\tuint64(decVals[2])\u003c\u003c50 |\n\t\t\tuint64(decVals[3])\u003c\u003c45 |\n\t\t\tuint64(decVals[4])\u003c\u003c40 |\n\t\t\tuint64(decVals[5])\u003c\u003c35 |\n\t\t\tuint64(decVals[6])\u003c\u003c30 |\n\t\t\tuint64(decVals[7])\u003c\u003c25 |\n\t\t\tuint64(decVals[8])\u003c\u003c20 |\n\t\t\tuint64(decVals[9])\u003c\u003c15 |\n\t\t\tuint64(decVals[10])\u003c\u003c10 |\n\t\t\tuint64(decVals[11])\u003c\u003c5 |\n\t\t\tuint64(decVals[12]), nil\n\t}\n}\n\nconst mask = 31\n\n// PutUint64 returns a cford32-encoded byte slice.\nfunc PutUint64(id uint64) [13]byte {\n\treturn [13]byte{\n\t\tencTable[id\u003e\u003e60\u0026mask|0x10], // specify full encoding\n\t\tencTable[id\u003e\u003e55\u0026mask],\n\t\tencTable[id\u003e\u003e50\u0026mask],\n\t\tencTable[id\u003e\u003e45\u0026mask],\n\t\tencTable[id\u003e\u003e40\u0026mask],\n\t\tencTable[id\u003e\u003e35\u0026mask],\n\t\tencTable[id\u003e\u003e30\u0026mask],\n\t\tencTable[id\u003e\u003e25\u0026mask],\n\t\tencTable[id\u003e\u003e20\u0026mask],\n\t\tencTable[id\u003e\u003e15\u0026mask],\n\t\tencTable[id\u003e\u003e10\u0026mask],\n\t\tencTable[id\u003e\u003e5\u0026mask],\n\t\tencTable[id\u0026mask],\n\t}\n}\n\n// PutUint64Lower returns a cford32-encoded byte array, swapping uppercase\n// letters with lowercase.\n//\n// For more information on how the value is encoded, see [Uint64].\nfunc PutUint64Lower(id uint64) [13]byte {\n\treturn [13]byte{\n\t\tencTableLower[id\u003e\u003e60\u0026mask|0x10],\n\t\tencTableLower[id\u003e\u003e55\u0026mask],\n\t\tencTableLower[id\u003e\u003e50\u0026mask],\n\t\tencTableLower[id\u003e\u003e45\u0026mask],\n\t\tencTableLower[id\u003e\u003e40\u0026mask],\n\t\tencTableLower[id\u003e\u003e35\u0026mask],\n\t\tencTableLower[id\u003e\u003e30\u0026mask],\n\t\tencTableLower[id\u003e\u003e25\u0026mask],\n\t\tencTableLower[id\u003e\u003e20\u0026mask],\n\t\tencTableLower[id\u003e\u003e15\u0026mask],\n\t\tencTableLower[id\u003e\u003e10\u0026mask],\n\t\tencTableLower[id\u003e\u003e5\u0026mask],\n\t\tencTableLower[id\u0026mask],\n\t}\n}\n\n// PutCompact returns a cford32-encoded byte slice, using the compact\n// representation of cford32 described in the package documentation where\n// possible (all values of id \u003c 1\u003c\u003c34). The lowercase encoding is used.\n//\n// The resulting byte slice will be 7 bytes long for all compact values,\n// and 13 bytes long for\nfunc PutCompact(id uint64) []byte {\n\treturn AppendCompact(id, nil)\n}\n\n// AppendCompact works like [PutCompact] but appends to the given byte slice\n// instead of allocating one anew.\nfunc AppendCompact(id uint64, b []byte) []byte {\n\tconst maxCompact = 1 \u003c\u003c 34\n\tif id \u003c maxCompact {\n\t\treturn append(b,\n\t\t\tencTableLower[id\u003e\u003e30\u0026mask],\n\t\t\tencTableLower[id\u003e\u003e25\u0026mask],\n\t\t\tencTableLower[id\u003e\u003e20\u0026mask],\n\t\t\tencTableLower[id\u003e\u003e15\u0026mask],\n\t\t\tencTableLower[id\u003e\u003e10\u0026mask],\n\t\t\tencTableLower[id\u003e\u003e5\u0026mask],\n\t\t\tencTableLower[id\u0026mask],\n\t\t)\n\t}\n\treturn append(b,\n\t\tencTableLower[id\u003e\u003e60\u0026mask|0x10],\n\t\tencTableLower[id\u003e\u003e55\u0026mask],\n\t\tencTableLower[id\u003e\u003e50\u0026mask],\n\t\tencTableLower[id\u003e\u003e45\u0026mask],\n\t\tencTableLower[id\u003e\u003e40\u0026mask],\n\t\tencTableLower[id\u003e\u003e35\u0026mask],\n\t\tencTableLower[id\u003e\u003e30\u0026mask],\n\t\tencTableLower[id\u003e\u003e25\u0026mask],\n\t\tencTableLower[id\u003e\u003e20\u0026mask],\n\t\tencTableLower[id\u003e\u003e15\u0026mask],\n\t\tencTableLower[id\u003e\u003e10\u0026mask],\n\t\tencTableLower[id\u003e\u003e5\u0026mask],\n\t\tencTableLower[id\u0026mask],\n\t)\n}\n\nfunc DecodedLen(n int) int {\n\treturn n/8*5 + n%8*5/8\n}\n\nfunc EncodedLen(n int) int {\n\treturn n/5*8 + (n%5*8+4)/5\n}\n\n// Encode encodes src using the encoding enc,\n// writing [EncodedLen](len(src)) bytes to dst.\n//\n// The encoding does not contain any padding, unlike Go's base32.\nfunc Encode(dst, src []byte) {\n\t// Copied from encoding/base32/base32.go (go1.22)\n\tif len(src) == 0 {\n\t\treturn\n\t}\n\n\tdi, si := 0, 0\n\tn := (len(src) / 5) * 5\n\tfor si \u003c n {\n\t\t// Combining two 32 bit loads allows the same code to be used\n\t\t// for 32 and 64 bit platforms.\n\t\thi := uint32(src[si+0])\u003c\u003c24 | uint32(src[si+1])\u003c\u003c16 | uint32(src[si+2])\u003c\u003c8 | uint32(src[si+3])\n\t\tlo := hi\u003c\u003c8 | uint32(src[si+4])\n\n\t\tdst[di+0] = encTable[(hi\u003e\u003e27)\u00260x1F]\n\t\tdst[di+1] = encTable[(hi\u003e\u003e22)\u00260x1F]\n\t\tdst[di+2] = encTable[(hi\u003e\u003e17)\u00260x1F]\n\t\tdst[di+3] = encTable[(hi\u003e\u003e12)\u00260x1F]\n\t\tdst[di+4] = encTable[(hi\u003e\u003e7)\u00260x1F]\n\t\tdst[di+5] = encTable[(hi\u003e\u003e2)\u00260x1F]\n\t\tdst[di+6] = encTable[(lo\u003e\u003e5)\u00260x1F]\n\t\tdst[di+7] = encTable[(lo)\u00260x1F]\n\n\t\tsi += 5\n\t\tdi += 8\n\t}\n\n\t// Add the remaining small block\n\tremain := len(src) - si\n\tif remain == 0 {\n\t\treturn\n\t}\n\n\t// Encode the remaining bytes in reverse order.\n\tval := uint32(0)\n\tswitch remain {\n\tcase 4:\n\t\tval |= uint32(src[si+3])\n\t\tdst[di+6] = encTable[val\u003c\u003c3\u00260x1F]\n\t\tdst[di+5] = encTable[val\u003e\u003e2\u00260x1F]\n\t\tfallthrough\n\tcase 3:\n\t\tval |= uint32(src[si+2]) \u003c\u003c 8\n\t\tdst[di+4] = encTable[val\u003e\u003e7\u00260x1F]\n\t\tfallthrough\n\tcase 2:\n\t\tval |= uint32(src[si+1]) \u003c\u003c 16\n\t\tdst[di+3] = encTable[val\u003e\u003e12\u00260x1F]\n\t\tdst[di+2] = encTable[val\u003e\u003e17\u00260x1F]\n\t\tfallthrough\n\tcase 1:\n\t\tval |= uint32(src[si+0]) \u003c\u003c 24\n\t\tdst[di+1] = encTable[val\u003e\u003e22\u00260x1F]\n\t\tdst[di+0] = encTable[val\u003e\u003e27\u00260x1F]\n\t}\n}\n\n// EncodeLower is like [Encode], but uses the lowercase\nfunc EncodeLower(dst, src []byte) {\n\t// Copied from encoding/base32/base32.go (go1.22)\n\tif len(src) == 0 {\n\t\treturn\n\t}\n\n\tdi, si := 0, 0\n\tn := (len(src) / 5) * 5\n\tfor si \u003c n {\n\t\t// Combining two 32 bit loads allows the same code to be used\n\t\t// for 32 and 64 bit platforms.\n\t\thi := uint32(src[si+0])\u003c\u003c24 | uint32(src[si+1])\u003c\u003c16 | uint32(src[si+2])\u003c\u003c8 | uint32(src[si+3])\n\t\tlo := hi\u003c\u003c8 | uint32(src[si+4])\n\n\t\tdst[di+0] = encTableLower[(hi\u003e\u003e27)\u00260x1F]\n\t\tdst[di+1] = encTableLower[(hi\u003e\u003e22)\u00260x1F]\n\t\tdst[di+2] = encTableLower[(hi\u003e\u003e17)\u00260x1F]\n\t\tdst[di+3] = encTableLower[(hi\u003e\u003e12)\u00260x1F]\n\t\tdst[di+4] = encTableLower[(hi\u003e\u003e7)\u00260x1F]\n\t\tdst[di+5] = encTableLower[(hi\u003e\u003e2)\u00260x1F]\n\t\tdst[di+6] = encTableLower[(lo\u003e\u003e5)\u00260x1F]\n\t\tdst[di+7] = encTableLower[(lo)\u00260x1F]\n\n\t\tsi += 5\n\t\tdi += 8\n\t}\n\n\t// Add the remaining small block\n\tremain := len(src) - si\n\tif remain == 0 {\n\t\treturn\n\t}\n\n\t// Encode the remaining bytes in reverse order.\n\tval := uint32(0)\n\tswitch remain {\n\tcase 4:\n\t\tval |= uint32(src[si+3])\n\t\tdst[di+6] = encTableLower[val\u003c\u003c3\u00260x1F]\n\t\tdst[di+5] = encTableLower[val\u003e\u003e2\u00260x1F]\n\t\tfallthrough\n\tcase 3:\n\t\tval |= uint32(src[si+2]) \u003c\u003c 8\n\t\tdst[di+4] = encTableLower[val\u003e\u003e7\u00260x1F]\n\t\tfallthrough\n\tcase 2:\n\t\tval |= uint32(src[si+1]) \u003c\u003c 16\n\t\tdst[di+3] = encTableLower[val\u003e\u003e12\u00260x1F]\n\t\tdst[di+2] = encTableLower[val\u003e\u003e17\u00260x1F]\n\t\tfallthrough\n\tcase 1:\n\t\tval |= uint32(src[si+0]) \u003c\u003c 24\n\t\tdst[di+1] = encTableLower[val\u003e\u003e22\u00260x1F]\n\t\tdst[di+0] = encTableLower[val\u003e\u003e27\u00260x1F]\n\t}\n}\n\n// AppendEncode appends the cford32 encoded src to dst\n// and returns the extended buffer.\nfunc AppendEncode(dst, src []byte) []byte {\n\tn := EncodedLen(len(src))\n\tdst = grow(dst, n)\n\tEncode(dst[len(dst):][:n], src)\n\treturn dst[:len(dst)+n]\n}\n\n// AppendEncodeLower appends the lowercase cford32 encoded src to dst\n// and returns the extended buffer.\nfunc AppendEncodeLower(dst, src []byte) []byte {\n\tn := EncodedLen(len(src))\n\tdst = grow(dst, n)\n\tEncodeLower(dst[len(dst):][:n], src)\n\treturn dst[:len(dst)+n]\n}\n\nfunc grow(s []byte, n int) []byte {\n\t// slices.Grow\n\tif n -= cap(s) - len(s); n \u003e 0 {\n\t\tnews := make([]byte, cap(s)+n)\n\t\tcopy(news[:cap(s)], s[:cap(s)])\n\t\treturn news[:len(s)]\n\t}\n\treturn s\n}\n\n// EncodeToString returns the cford32 encoding of src.\nfunc EncodeToString(src []byte) string {\n\tbuf := make([]byte, EncodedLen(len(src)))\n\tEncode(buf, src)\n\treturn string(buf)\n}\n\n// EncodeToStringLower returns the cford32 lowercase encoding of src.\nfunc EncodeToStringLower(src []byte) string {\n\tbuf := make([]byte, EncodedLen(len(src)))\n\tEncodeLower(buf, src)\n\treturn string(buf)\n}\n\nfunc decode(dst, src []byte) (n int, err error) {\n\tdsti := 0\n\tolen := len(src)\n\n\tfor len(src) \u003e 0 {\n\t\t// Decode quantum using the base32 alphabet\n\t\tvar dbuf [8]byte\n\t\tdlen := 8\n\n\t\tfor j := 0; j \u003c 8; {\n\t\t\tif len(src) == 0 {\n\t\t\t\t// We have reached the end and are not expecting any padding\n\t\t\t\tdlen = j\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tin := src[0]\n\t\t\tsrc = src[1:]\n\t\t\tdbuf[j] = decTable[in]\n\t\t\tif dbuf[j] == 0xFF {\n\t\t\t\treturn n, CorruptInputError(olen - len(src) - 1)\n\t\t\t}\n\t\t\tj++\n\t\t}\n\n\t\t// Pack 8x 5-bit source blocks into 5 byte destination\n\t\t// quantum\n\t\tswitch dlen {\n\t\tcase 8:\n\t\t\tdst[dsti+4] = dbuf[6]\u003c\u003c5 | dbuf[7]\n\t\t\tn++\n\t\t\tfallthrough\n\t\tcase 7:\n\t\t\tdst[dsti+3] = dbuf[4]\u003c\u003c7 | dbuf[5]\u003c\u003c2 | dbuf[6]\u003e\u003e3\n\t\t\tn++\n\t\t\tfallthrough\n\t\tcase 5:\n\t\t\tdst[dsti+2] = dbuf[3]\u003c\u003c4 | dbuf[4]\u003e\u003e1\n\t\t\tn++\n\t\t\tfallthrough\n\t\tcase 4:\n\t\t\tdst[dsti+1] = dbuf[1]\u003c\u003c6 | dbuf[2]\u003c\u003c1 | dbuf[3]\u003e\u003e4\n\t\t\tn++\n\t\t\tfallthrough\n\t\tcase 2:\n\t\t\tdst[dsti+0] = dbuf[0]\u003c\u003c3 | dbuf[1]\u003e\u003e2\n\t\t\tn++\n\t\t}\n\t\tdsti += 5\n\t}\n\treturn n, nil\n}\n\ntype encoder struct {\n\terr error\n\tw io.Writer\n\tenc func(dst, src []byte)\n\tbuf [5]byte // buffered data waiting to be encoded\n\tnbuf int // number of bytes in buf\n\tout [1024]byte // output buffer\n}\n\nfunc NewEncoder(w io.Writer) io.WriteCloser {\n\treturn \u0026encoder{w: w, enc: Encode}\n}\n\nfunc NewEncoderLower(w io.Writer) io.WriteCloser {\n\treturn \u0026encoder{w: w, enc: EncodeLower}\n}\n\nfunc (e *encoder) Write(p []byte) (n int, err error) {\n\tif e.err != nil {\n\t\treturn 0, e.err\n\t}\n\n\t// Leading fringe.\n\tif e.nbuf \u003e 0 {\n\t\tvar i int\n\t\tfor i = 0; i \u003c len(p) \u0026\u0026 e.nbuf \u003c 5; i++ {\n\t\t\te.buf[e.nbuf] = p[i]\n\t\t\te.nbuf++\n\t\t}\n\t\tn += i\n\t\tp = p[i:]\n\t\tif e.nbuf \u003c 5 {\n\t\t\treturn\n\t\t}\n\t\te.enc(e.out[0:], e.buf[0:])\n\t\tif _, e.err = e.w.Write(e.out[0:8]); e.err != nil {\n\t\t\treturn n, e.err\n\t\t}\n\t\te.nbuf = 0\n\t}\n\n\t// Large interior chunks.\n\tfor len(p) \u003e= 5 {\n\t\tnn := len(e.out) / 8 * 5\n\t\tif nn \u003e len(p) {\n\t\t\tnn = len(p)\n\t\t\tnn -= nn % 5\n\t\t}\n\t\te.enc(e.out[0:], p[0:nn])\n\t\tif _, e.err = e.w.Write(e.out[0 : nn/5*8]); e.err != nil {\n\t\t\treturn n, e.err\n\t\t}\n\t\tn += nn\n\t\tp = p[nn:]\n\t}\n\n\t// Trailing fringe.\n\tcopy(e.buf[:], p)\n\te.nbuf = len(p)\n\tn += len(p)\n\treturn\n}\n\n// Close flushes any pending output from the encoder.\n// It is an error to call Write after calling Close.\nfunc (e *encoder) Close() error {\n\t// If there's anything left in the buffer, flush it out\n\tif e.err == nil \u0026\u0026 e.nbuf \u003e 0 {\n\t\te.enc(e.out[0:], e.buf[0:e.nbuf])\n\t\tencodedLen := EncodedLen(e.nbuf)\n\t\te.nbuf = 0\n\t\t_, e.err = e.w.Write(e.out[0:encodedLen])\n\t}\n\treturn e.err\n}\n\n// Decode decodes src using cford32. It writes at most\n// [DecodedLen](len(src)) bytes to dst and returns the number of bytes\n// written. If src contains invalid cford32 data, it will return the\n// number of bytes successfully written and [CorruptInputError].\n// Newline characters (\\r and \\n) are ignored.\nfunc Decode(dst, src []byte) (n int, err error) {\n\tbuf := make([]byte, len(src))\n\tl := stripNewlines(buf, src)\n\treturn decode(dst, buf[:l])\n}\n\n// AppendDecode appends the cford32 decoded src to dst\n// and returns the extended buffer.\n// If the input is malformed, it returns the partially decoded src and an error.\nfunc AppendDecode(dst, src []byte) ([]byte, error) {\n\tn := DecodedLen(len(src))\n\n\tdst = grow(dst, n)\n\tdstsl := dst[len(dst) : len(dst)+n]\n\tn, err := Decode(dstsl, src)\n\treturn dst[:len(dst)+n], err\n}\n\n// DecodeString returns the bytes represented by the cford32 string s.\nfunc DecodeString(s string) ([]byte, error) {\n\tbuf := []byte(s)\n\tl := stripNewlines(buf, buf)\n\tn, err := decode(buf, buf[:l])\n\treturn buf[:n], err\n}\n\n// stripNewlines removes newline characters and returns the number\n// of non-newline characters copied to dst.\nfunc stripNewlines(dst, src []byte) int {\n\toffset := 0\n\tfor _, b := range src {\n\t\tif b == '\\r' || b == '\\n' {\n\t\t\tcontinue\n\t\t}\n\t\tdst[offset] = b\n\t\toffset++\n\t}\n\treturn offset\n}\n\ntype decoder struct {\n\terr error\n\tr io.Reader\n\tbuf [1024]byte // leftover input\n\tnbuf int\n\tout []byte // leftover decoded output\n\toutbuf [1024 / 8 * 5]byte\n}\n\n// NewDecoder constructs a new base32 stream decoder.\nfunc NewDecoder(r io.Reader) io.Reader {\n\treturn \u0026decoder{r: \u0026newlineFilteringReader{r}}\n}\n\nfunc readEncodedData(r io.Reader, buf []byte) (n int, err error) {\n\tfor n \u003c 1 \u0026\u0026 err == nil {\n\t\tvar nn int\n\t\tnn, err = r.Read(buf[n:])\n\t\tn += nn\n\t}\n\treturn\n}\n\nfunc (d *decoder) Read(p []byte) (n int, err error) {\n\t// Use leftover decoded output from last read.\n\tif len(d.out) \u003e 0 {\n\t\tn = copy(p, d.out)\n\t\td.out = d.out[n:]\n\t\tif len(d.out) == 0 {\n\t\t\treturn n, d.err\n\t\t}\n\t\treturn n, nil\n\t}\n\n\tif d.err != nil {\n\t\treturn 0, d.err\n\t}\n\n\t// Read nn bytes from input, bounded [8,len(d.buf)]\n\tnn := (len(p)/5 + 1) * 8\n\tif nn \u003e len(d.buf) {\n\t\tnn = len(d.buf)\n\t}\n\n\tnn, d.err = readEncodedData(d.r, d.buf[d.nbuf:nn])\n\td.nbuf += nn\n\tif d.nbuf \u003c 1 {\n\t\treturn 0, d.err\n\t}\n\n\t// Decode chunk into p, or d.out and then p if p is too small.\n\tnr := d.nbuf\n\tif d.err != io.EOF \u0026\u0026 nr%8 != 0 {\n\t\tnr -= nr % 8\n\t}\n\tnw := DecodedLen(d.nbuf)\n\n\tif nw \u003e len(p) {\n\t\tnw, err = decode(d.outbuf[0:], d.buf[0:nr])\n\t\td.out = d.outbuf[0:nw]\n\t\tn = copy(p, d.out)\n\t\td.out = d.out[n:]\n\t} else {\n\t\tn, err = decode(p, d.buf[0:nr])\n\t}\n\td.nbuf -= nr\n\tfor i := 0; i \u003c d.nbuf; i++ {\n\t\td.buf[i] = d.buf[i+nr]\n\t}\n\n\tif err != nil \u0026\u0026 (d.err == nil || d.err == io.EOF) {\n\t\td.err = err\n\t}\n\n\tif len(d.out) \u003e 0 {\n\t\t// We cannot return all the decoded bytes to the caller in this\n\t\t// invocation of Read, so we return a nil error to ensure that Read\n\t\t// will be called again. The error stored in d.err, if any, will be\n\t\t// returned with the last set of decoded bytes.\n\t\treturn n, nil\n\t}\n\n\treturn n, d.err\n}\n\ntype newlineFilteringReader struct {\n\twrapped io.Reader\n}\n\nfunc (r *newlineFilteringReader) Read(p []byte) (int, error) {\n\tn, err := r.wrapped.Read(p)\n\tfor n \u003e 0 {\n\t\ts := p[0:n]\n\t\toffset := stripNewlines(s, s)\n\t\tif err != nil || offset \u003e 0 {\n\t\t\treturn offset, err\n\t\t}\n\t\t// Previous buffer entirely whitespace, read again\n\t\tn, err = r.wrapped.Read(p)\n\t}\n\treturn n, err\n}\n" + }, + { + "name": "cford32_test.gno", + "body": "package cford32\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestCompactRoundtrip(t *testing.T) {\n\tbuf := make([]byte, 13)\n\tprev := make([]byte, 13)\n\tfor i := uint64(0); i \u003c (1 \u003c\u003c 12); i++ {\n\t\tres := AppendCompact(i, buf[:0])\n\t\tback, err := Uint64(res)\n\t\ttestEqual(t, \"Uint64(%q) = (%d, %v), want %v\", string(res), back, err, nil)\n\t\ttestEqual(t, \"Uint64(%q) = %d, want %v\", string(res), back, i)\n\n\t\ttestEqual(t, \"bytes.Compare(prev, res) = %d, want %d\", bytes.Compare(prev, res), -1)\n\t\tprev, buf = res, prev\n\t}\n\tfor i := uint64(1\u003c\u003c34 - 1024); i \u003c (1\u003c\u003c34 + 1024); i++ {\n\t\tres := AppendCompact(i, buf[:0])\n\t\tback, err := Uint64(res)\n\t\t// println(string(res))\n\t\ttestEqual(t, \"Uint64(%q) = (%d, %v), want %v\", string(res), back, err, nil)\n\t\ttestEqual(t, \"Uint64(%q) = %d, want %v\", string(res), back, i)\n\n\t\ttestEqual(t, \"bytes.Compare(prev, res) = %d, want %d\", bytes.Compare(prev, res), -1)\n\t\tprev, buf = res, prev\n\t}\n\tfor i := uint64(1\u003c\u003c64 - 5000); i != 0; i++ {\n\t\tres := AppendCompact(i, buf[:0])\n\t\tback, err := Uint64(res)\n\t\ttestEqual(t, \"Uint64(%q) = (%d, %v), want %v\", string(res), back, err, nil)\n\t\ttestEqual(t, \"Uint64(%q) = %d, want %v\", string(res), back, i)\n\n\t\ttestEqual(t, \"bytes.Compare(prev, res) = %d, want %d\", bytes.Compare(prev, res), -1)\n\t\tprev, buf = res, prev\n\t}\n}\n\nfunc BenchmarkCompact(b *testing.B) {\n\tbuf := make([]byte, 13)\n\tfor i := 0; i \u003c b.N; i++ {\n\t\t_ = AppendCompact(uint64(i), buf[:0])\n\t}\n}\n\ntype testpair struct {\n\tdecoded, encoded string\n}\n\nvar pairs = []testpair{\n\t{\"\", \"\"},\n\t{\"f\", \"CR\"},\n\t{\"fo\", \"CSQG\"},\n\t{\"foo\", \"CSQPY\"},\n\t{\"foob\", \"CSQPYRG\"},\n\t{\"fooba\", \"CSQPYRK1\"},\n\t{\"foobar\", \"CSQPYRK1E8\"},\n\n\t{\"sure.\", \"EDTQ4S9E\"},\n\t{\"sure\", \"EDTQ4S8\"},\n\t{\"sur\", \"EDTQ4\"},\n\t{\"su\", \"EDTG\"},\n\t{\"leasure.\", \"DHJP2WVNE9JJW\"},\n\t{\"easure.\", \"CNGQ6XBJCMQ0\"},\n\t{\"asure.\", \"C5SQAWK55R\"},\n}\n\nvar bigtest = testpair{\n\t\"Twas brillig, and the slithy toves\",\n\t\"AHVP2WS0C9S6JV3CD5KJR831DSJ20X38CMG76V39EHM7J83MDXV6AWR\",\n}\n\nfunc testEqual(t *testing.T, msg string, args ...interface{}) bool {\n\tt.Helper()\n\tif args[len(args)-2] != args[len(args)-1] {\n\t\tt.Errorf(msg, args...)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc TestEncode(t *testing.T) {\n\tfor _, p := range pairs {\n\t\tgot := EncodeToString([]byte(p.decoded))\n\t\ttestEqual(t, \"Encode(%q) = %q, want %q\", p.decoded, got, p.encoded)\n\t\tdst := AppendEncode([]byte(\"lead\"), []byte(p.decoded))\n\t\ttestEqual(t, `AppendEncode(\"lead\", %q) = %q, want %q`, p.decoded, string(dst), \"lead\"+p.encoded)\n\t}\n}\n\nfunc TestEncoder(t *testing.T) {\n\tfor _, p := range pairs {\n\t\tbb := \u0026strings.Builder{}\n\t\tencoder := NewEncoder(bb)\n\t\tencoder.Write([]byte(p.decoded))\n\t\tencoder.Close()\n\t\ttestEqual(t, \"Encode(%q) = %q, want %q\", p.decoded, bb.String(), p.encoded)\n\t}\n}\n\nfunc TestEncoderBuffering(t *testing.T) {\n\tinput := []byte(bigtest.decoded)\n\tfor bs := 1; bs \u003c= 12; bs++ {\n\t\tbb := \u0026strings.Builder{}\n\t\tencoder := NewEncoder(bb)\n\t\tfor pos := 0; pos \u003c len(input); pos += bs {\n\t\t\tend := pos + bs\n\t\t\tif end \u003e len(input) {\n\t\t\t\tend = len(input)\n\t\t\t}\n\t\t\tn, err := encoder.Write(input[pos:end])\n\t\t\ttestEqual(t, \"Write(%q) gave error %v, want %v\", input[pos:end], err, error(nil))\n\t\t\ttestEqual(t, \"Write(%q) gave length %v, want %v\", input[pos:end], n, end-pos)\n\t\t}\n\t\terr := encoder.Close()\n\t\ttestEqual(t, \"Close gave error %v, want %v\", err, error(nil))\n\t\ttestEqual(t, \"Encoding/%d of %q = %q, want %q\", bs, bigtest.decoded, bb.String(), bigtest.encoded)\n\t}\n}\n\nfunc TestDecode(t *testing.T) {\n\tfor _, p := range pairs {\n\t\tdbuf := make([]byte, DecodedLen(len(p.encoded)))\n\t\tcount, err := decode(dbuf, []byte(p.encoded))\n\t\ttestEqual(t, \"Decode(%q) = error %v, want %v\", p.encoded, err, error(nil))\n\t\ttestEqual(t, \"Decode(%q) = length %v, want %v\", p.encoded, count, len(p.decoded))\n\t\ttestEqual(t, \"Decode(%q) = %q, want %q\", p.encoded, string(dbuf[0:count]), p.decoded)\n\n\t\tdbuf, err = DecodeString(p.encoded)\n\t\ttestEqual(t, \"DecodeString(%q) = error %v, want %v\", p.encoded, err, error(nil))\n\t\ttestEqual(t, \"DecodeString(%q) = %q, want %q\", p.encoded, string(dbuf), p.decoded)\n\n\t\t// XXX: https://github.com/gnolang/gno/issues/1570\n\t\tdst, err := AppendDecode(append([]byte(nil), []byte(\"lead\")...), []byte(p.encoded))\n\t\ttestEqual(t, \"AppendDecode(%q) = error %v, want %v\", p.encoded, err, error(nil))\n\t\ttestEqual(t, `AppendDecode(\"lead\", %q) = %q, want %q`, p.encoded, string(dst), \"lead\"+p.decoded)\n\n\t\tdst2, err := AppendDecode(dst[:0:len(p.decoded)], []byte(p.encoded))\n\t\ttestEqual(t, \"AppendDecode(%q) = error %v, want %v\", p.encoded, err, error(nil))\n\t\ttestEqual(t, `AppendDecode(\"\", %q) = %q, want %q`, p.encoded, string(dst2), p.decoded)\n\t\t// XXX: https://github.com/gnolang/gno/issues/1569\n\t\t// old used \u0026dst2[0] != \u0026dst[0] as a check.\n\t\tif len(dst) \u003e 0 \u0026\u0026 len(dst2) \u003e 0 \u0026\u0026 cap(dst2) != len(p.decoded) {\n\t\t\tt.Errorf(\"unexpected capacity growth: got %d, want %d\", cap(dst2), len(p.decoded))\n\t\t}\n\t}\n}\n\n// A minimal variation on strings.Reader.\n// Here, we return a io.EOF immediately on Read if the read has reached the end\n// of the reader. It's used to simplify TestDecoder.\ntype stringReader struct {\n\ts string\n\ti int64\n}\n\nfunc (r *stringReader) Read(b []byte) (n int, err error) {\n\tif r.i \u003e= int64(len(r.s)) {\n\t\treturn 0, io.EOF\n\t}\n\tn = copy(b, r.s[r.i:])\n\tr.i += int64(n)\n\tif r.i \u003e= int64(len(r.s)) {\n\t\treturn n, io.EOF\n\t}\n\treturn\n}\n\nfunc TestDecoder(t *testing.T) {\n\tfor _, p := range pairs {\n\t\tdecoder := NewDecoder(\u0026stringReader{p.encoded, 0})\n\t\tdbuf := make([]byte, DecodedLen(len(p.encoded)))\n\t\tcount, err := decoder.Read(dbuf)\n\t\tif err != nil \u0026\u0026 err != io.EOF {\n\t\t\tt.Fatal(\"Read failed\", err)\n\t\t}\n\t\ttestEqual(t, \"Read from %q = length %v, want %v\", p.encoded, count, len(p.decoded))\n\t\ttestEqual(t, \"Decoding of %q = %q, want %q\", p.encoded, string(dbuf[0:count]), p.decoded)\n\t\tif err != io.EOF {\n\t\t\t_, err = decoder.Read(dbuf)\n\t\t}\n\t\ttestEqual(t, \"Read from %q = %v, want %v\", p.encoded, err, io.EOF)\n\t}\n}\n\ntype badReader struct {\n\tdata []byte\n\terrs []error\n\tcalled int\n\tlimit int\n}\n\n// Populates p with data, returns a count of the bytes written and an\n// error. The error returned is taken from badReader.errs, with each\n// invocation of Read returning the next error in this slice, or io.EOF,\n// if all errors from the slice have already been returned. The\n// number of bytes returned is determined by the size of the input buffer\n// the test passes to decoder.Read and will be a multiple of 8, unless\n// badReader.limit is non zero.\nfunc (b *badReader) Read(p []byte) (int, error) {\n\tlim := len(p)\n\tif b.limit != 0 \u0026\u0026 b.limit \u003c lim {\n\t\tlim = b.limit\n\t}\n\tif len(b.data) \u003c lim {\n\t\tlim = len(b.data)\n\t}\n\tfor i := range p[:lim] {\n\t\tp[i] = b.data[i]\n\t}\n\tb.data = b.data[lim:]\n\terr := io.EOF\n\tif b.called \u003c len(b.errs) {\n\t\terr = b.errs[b.called]\n\t}\n\tb.called++\n\treturn lim, err\n}\n\n// TestIssue20044 tests that decoder.Read behaves correctly when the caller\n// supplied reader returns an error.\nfunc TestIssue20044(t *testing.T) {\n\tbadErr := errors.New(\"bad reader error\")\n\ttestCases := []struct {\n\t\tr badReader\n\t\tres string\n\t\terr error\n\t\tdbuflen int\n\t}{\n\t\t// Check valid input data accompanied by an error is processed and the error is propagated.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"d1jprv3fexqq4v34\"), errs: []error{badErr}},\n\t\t\tres: \"helloworld\", err: badErr,\n\t\t},\n\t\t// Check a read error accompanied by input data consisting of newlines only is propagated.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"\\n\\n\\n\\n\\n\\n\\n\\n\"), errs: []error{badErr, nil}},\n\t\t\tres: \"\", err: badErr,\n\t\t},\n\t\t// Reader will be called twice. The first time it will return 8 newline characters. The\n\t\t// second time valid base32 encoded data and an error. The data should be decoded\n\t\t// correctly and the error should be propagated.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"\\n\\n\\n\\n\\n\\n\\n\\nd1jprv3fexqq4v34\"), errs: []error{nil, badErr}},\n\t\t\tres: \"helloworld\", err: badErr, dbuflen: 8,\n\t\t},\n\t\t// Reader returns invalid input data (too short) and an error. Verify the reader\n\t\t// error is returned.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"c\"), errs: []error{badErr}},\n\t\t\tres: \"\", err: badErr,\n\t\t},\n\t\t// Reader returns invalid input data (too short) but no error. Verify io.ErrUnexpectedEOF\n\t\t// is returned.\n\t\t// NOTE(thehowl): I don't think this should applyto us?\n\t\t/* {\n\t\t\tr: badReader{data: []byte(\"c\"), errs: []error{nil}},\n\t\t\tres: \"\", err: io.ErrUnexpectedEOF,\n\t\t},*/\n\t\t// Reader returns invalid input data and an error. Verify the reader and not the\n\t\t// decoder error is returned.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"cu\"), errs: []error{badErr}},\n\t\t\tres: \"\", err: badErr,\n\t\t},\n\t\t// Reader returns valid data and io.EOF. Check data is decoded and io.EOF is propagated.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"csqpyrk1\"), errs: []error{io.EOF}},\n\t\t\tres: \"fooba\", err: io.EOF,\n\t\t},\n\t\t// Check errors are properly reported when decoder.Read is called multiple times.\n\t\t// decoder.Read will be called 8 times, badReader.Read will be called twice, returning\n\t\t// valid data both times but an error on the second call.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"dhjp2wvne9jjwc9g\"), errs: []error{nil, badErr}},\n\t\t\tres: \"leasure.10\", err: badErr, dbuflen: 1,\n\t\t},\n\t\t// Check io.EOF is properly reported when decoder.Read is called multiple times.\n\t\t// decoder.Read will be called 8 times, badReader.Read will be called twice, returning\n\t\t// valid data both times but io.EOF on the second call.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"dhjp2wvne9jjw\"), errs: []error{nil, io.EOF}},\n\t\t\tres: \"leasure.\", err: io.EOF, dbuflen: 1,\n\t\t},\n\t\t// The following two test cases check that errors are propagated correctly when more than\n\t\t// 8 bytes are read at a time.\n\t\t{\n\t\t\tr: badReader{data: []byte(\"dhjp2wvne9jjw\"), errs: []error{io.EOF}},\n\t\t\tres: \"leasure.\", err: io.EOF, dbuflen: 11,\n\t\t},\n\t\t{\n\t\t\tr: badReader{data: []byte(\"dhjp2wvne9jjwc9g\"), errs: []error{badErr}},\n\t\t\tres: \"leasure.10\", err: badErr, dbuflen: 11,\n\t\t},\n\t\t// Check that errors are correctly propagated when the reader returns valid bytes in\n\t\t// groups that are not divisible by 8. The first read will return 11 bytes and no\n\t\t// error. The second will return 7 and an error. The data should be decoded correctly\n\t\t// and the error should be propagated.\n\t\t// NOTE(thehowl): again, this is on the assumption that this is padded, and it's not.\n\t\t/* {\n\t\t\tr: badReader{data: []byte(\"dhjp2wvne9jjw\"), errs: []error{nil, badErr}, limit: 11},\n\t\t\tres: \"leasure.\", err: badErr,\n\t\t}, */\n\t}\n\n\tfor idx, tc := range testCases {\n\t\tt.Run(fmt.Sprintf(\"%d-%s\", idx, string(tc.res)), func(t *testing.T) {\n\t\t\tinput := tc.r.data\n\t\t\tdecoder := NewDecoder(\u0026tc.r)\n\t\t\tvar dbuflen int\n\t\t\tif tc.dbuflen \u003e 0 {\n\t\t\t\tdbuflen = tc.dbuflen\n\t\t\t} else {\n\t\t\t\tdbuflen = DecodedLen(len(input))\n\t\t\t}\n\t\t\tdbuf := make([]byte, dbuflen)\n\t\t\tvar err error\n\t\t\tvar res []byte\n\t\t\tfor err == nil {\n\t\t\t\tvar n int\n\t\t\t\tn, err = decoder.Read(dbuf)\n\t\t\t\tif n \u003e 0 {\n\t\t\t\t\tres = append(res, dbuf[:n]...)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestEqual(t, \"Decoding of %q = %q, want %q\", string(input), string(res), tc.res)\n\t\t\ttestEqual(t, \"Decoding of %q err = %v, expected %v\", string(input), err, tc.err)\n\t\t})\n\t}\n}\n\n// TestDecoderError verifies decode errors are propagated when there are no read\n// errors.\nfunc TestDecoderError(t *testing.T) {\n\tfor _, readErr := range []error{io.EOF, nil} {\n\t\tinput := \"ucsqpyrk1u\"\n\t\tdbuf := make([]byte, DecodedLen(len(input)))\n\t\tbr := badReader{data: []byte(input), errs: []error{readErr}}\n\t\tdecoder := NewDecoder(\u0026br)\n\t\tn, err := decoder.Read(dbuf)\n\t\ttestEqual(t, \"Read after EOF, n = %d, expected %d\", n, 0)\n\t\tif _, ok := err.(CorruptInputError); !ok {\n\t\t\tt.Errorf(\"Corrupt input error expected. Found %T\", err)\n\t\t}\n\t}\n}\n\n// TestReaderEOF ensures decoder.Read behaves correctly when input data is\n// exhausted.\nfunc TestReaderEOF(t *testing.T) {\n\tfor _, readErr := range []error{io.EOF, nil} {\n\t\tinput := \"MZXW6YTB\"\n\t\tbr := badReader{data: []byte(input), errs: []error{nil, readErr}}\n\t\tdecoder := NewDecoder(\u0026br)\n\t\tdbuf := make([]byte, DecodedLen(len(input)))\n\t\tn, err := decoder.Read(dbuf)\n\t\ttestEqual(t, \"Decoding of %q err = %v, expected %v\", input, err, error(nil))\n\t\tn, err = decoder.Read(dbuf)\n\t\ttestEqual(t, \"Read after EOF, n = %d, expected %d\", n, 0)\n\t\ttestEqual(t, \"Read after EOF, err = %v, expected %v\", err, io.EOF)\n\t\tn, err = decoder.Read(dbuf)\n\t\ttestEqual(t, \"Read after EOF, n = %d, expected %d\", n, 0)\n\t\ttestEqual(t, \"Read after EOF, err = %v, expected %v\", err, io.EOF)\n\t}\n}\n\nfunc TestDecoderBuffering(t *testing.T) {\n\tfor bs := 1; bs \u003c= 12; bs++ {\n\t\tdecoder := NewDecoder(strings.NewReader(bigtest.encoded))\n\t\tbuf := make([]byte, len(bigtest.decoded)+12)\n\t\tvar total int\n\t\tvar n int\n\t\tvar err error\n\t\tfor total = 0; total \u003c len(bigtest.decoded) \u0026\u0026 err == nil; {\n\t\t\tn, err = decoder.Read(buf[total : total+bs])\n\t\t\ttotal += n\n\t\t}\n\t\tif err != nil \u0026\u0026 err != io.EOF {\n\t\t\tt.Errorf(\"Read from %q at pos %d = %d, unexpected error %v\", bigtest.encoded, total, n, err)\n\t\t}\n\t\ttestEqual(t, \"Decoding/%d of %q = %q, want %q\", bs, bigtest.encoded, string(buf[0:total]), bigtest.decoded)\n\t}\n}\n\nfunc TestDecodeCorrupt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput string\n\t\toffset int // -1 means no corruption.\n\t}{\n\t\t{\"\", -1},\n\t\t{\"iIoOlL\", -1},\n\t\t{\"!!!!\", 0},\n\t\t{\"uxp10\", 0},\n\t\t{\"x===\", 1},\n\t\t{\"AA=A====\", 2},\n\t\t{\"AAA=AAAA\", 3},\n\t\t// Much fewer cases compared to Go as there are much fewer cases where input\n\t\t// can be \"corrupted\".\n\t}\n\tfor _, tc := range testCases {\n\t\tdbuf := make([]byte, DecodedLen(len(tc.input)))\n\t\t_, err := Decode(dbuf, []byte(tc.input))\n\t\tif tc.offset == -1 {\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"Decoder wrongly detected corruption in\", tc.input)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tswitch err := err.(type) {\n\t\tcase CorruptInputError:\n\t\t\ttestEqual(t, \"Corruption in %q at offset %v, want %v\", tc.input, int(err), tc.offset)\n\t\tdefault:\n\t\t\tt.Error(\"Decoder failed to detect corruption in\", tc)\n\t\t}\n\t}\n}\n\nfunc TestBig(t *testing.T) {\n\tn := 3*1000 + 1\n\traw := make([]byte, n)\n\tconst alpha = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\tfor i := 0; i \u003c n; i++ {\n\t\traw[i] = alpha[i%len(alpha)]\n\t}\n\tencoded := new(bytes.Buffer)\n\tw := NewEncoder(encoded)\n\tnn, err := w.Write(raw)\n\tif nn != n || err != nil {\n\t\tt.Fatalf(\"Encoder.Write(raw) = %d, %v want %d, nil\", nn, err, n)\n\t}\n\terr = w.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"Encoder.Close() = %v want nil\", err)\n\t}\n\tdecoded, err := io.ReadAll(NewDecoder(encoded))\n\tif err != nil {\n\t\tt.Fatalf(\"io.ReadAll(NewDecoder(...)): %v\", err)\n\t}\n\n\tif !bytes.Equal(raw, decoded) {\n\t\tvar i int\n\t\tfor i = 0; i \u003c len(decoded) \u0026\u0026 i \u003c len(raw); i++ {\n\t\t\tif decoded[i] != raw[i] {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tt.Errorf(\"Decode(Encode(%d-byte string)) failed at offset %d\", n, i)\n\t}\n}\n\nfunc testStringEncoding(t *testing.T, expected string, examples []string) {\n\tfor _, e := range examples {\n\t\tbuf, err := DecodeString(e)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Decode(%q) failed: %v\", e, err)\n\t\t\tcontinue\n\t\t}\n\t\tif s := string(buf); s != expected {\n\t\t\tt.Errorf(\"Decode(%q) = %q, want %q\", e, s, expected)\n\t\t}\n\t}\n}\n\nfunc TestNewLineCharacters(t *testing.T) {\n\t// Each of these should decode to the string \"sure\", without errors.\n\texamples := []string{\n\t\t\"EDTQ4S8\",\n\t\t\"EDTQ4S8\\r\",\n\t\t\"EDTQ4S8\\n\",\n\t\t\"EDTQ4S8\\r\\n\",\n\t\t\"EDTQ4S\\r\\n8\",\n\t\t\"EDT\\rQ4S\\n8\",\n\t\t\"edt\\nq4s\\r8\",\n\t\t\"edt\\nq4s8\",\n\t\t\"EDTQ4S\\n8\",\n\t}\n\ttestStringEncoding(t, \"sure\", examples)\n}\n\nfunc BenchmarkEncode(b *testing.B) {\n\tdata := make([]byte, 8192)\n\tbuf := make([]byte, EncodedLen(len(data)))\n\tb.SetBytes(int64(len(data)))\n\tfor i := 0; i \u003c b.N; i++ {\n\t\tEncode(buf, data)\n\t}\n}\n\nfunc BenchmarkEncodeToString(b *testing.B) {\n\tdata := make([]byte, 8192)\n\tb.SetBytes(int64(len(data)))\n\tfor i := 0; i \u003c b.N; i++ {\n\t\tEncodeToString(data)\n\t}\n}\n\nfunc BenchmarkDecode(b *testing.B) {\n\tdata := make([]byte, EncodedLen(8192))\n\tEncode(data, make([]byte, 8192))\n\tbuf := make([]byte, 8192)\n\tb.SetBytes(int64(len(data)))\n\tfor i := 0; i \u003c b.N; i++ {\n\t\tDecode(buf, data)\n\t}\n}\n\nfunc BenchmarkDecodeString(b *testing.B) {\n\tdata := EncodeToString(make([]byte, 8192))\n\tb.SetBytes(int64(len(data)))\n\tfor i := 0; i \u003c b.N; i++ {\n\t\tDecodeString(data)\n\t}\n}\n\n/* TODO: rewrite without using goroutines\nfunc TestBufferedDecodingSameError(t *testing.T) {\n\ttestcases := []struct {\n\t\tprefix string\n\t\tchunkCombinations [][]string\n\t\texpected error\n\t}{\n\t\t// Normal case, this is valid input\n\t\t{\"helloworld\", [][]string{\n\t\t\t{\"D1JP\", \"RV3F\", \"EXQQ\", \"4V34\"},\n\t\t\t{\"D1JPRV3FEXQQ4V34\"},\n\t\t\t{\"D1J\", \"PRV\", \"3FE\", \"XQQ\", \"4V3\", \"4\"},\n\t\t\t{\"D1JPRV3FEXQQ4V\", \"34\"},\n\t\t}, nil},\n\n\t\t// Normal case, this is valid input\n\t\t{\"fooba\", [][]string{\n\t\t\t{\"CSQPYRK1\"},\n\t\t\t{\"CSQPYRK\", \"1\"},\n\t\t\t{\"CSQPYR\", \"K1\"},\n\t\t\t{\"CSQPY\", \"RK1\"},\n\t\t\t{\"CSQPY\", \"RK\", \"1\"},\n\t\t\t{\"CSQPY\", \"RK1\"},\n\t\t\t{\"CSQP\", \"YR\", \"K1\"},\n\t\t}, nil},\n\n\t\t// NOTE: many test cases have been removed as we don't return ErrUnexpectedEOF.\n\t}\n\n\tfor _, testcase := range testcases {\n\t\tfor _, chunks := range testcase.chunkCombinations {\n\t\t\tpr, pw := io.Pipe()\n\n\t\t\t// Write the encoded chunks into the pipe\n\t\t\tgo func() {\n\t\t\t\tfor _, chunk := range chunks {\n\t\t\t\t\tpw.Write([]byte(chunk))\n\t\t\t\t}\n\t\t\t\tpw.Close()\n\t\t\t}()\n\n\t\t\tdecoder := NewDecoder(pr)\n\t\t\tback, err := io.ReadAll(decoder)\n\n\t\t\tif err != testcase.expected {\n\t\t\t\tt.Errorf(\"Expected %v, got %v; case %s %+v\", testcase.expected, err, testcase.prefix, chunks)\n\t\t\t}\n\t\t\tif testcase.expected == nil {\n\t\t\t\ttestEqual(t, \"Decode from NewDecoder(chunkReader(%v)) = %q, want %q\", chunks, string(back), testcase.prefix)\n\t\t\t}\n\t\t}\n\t}\n}\n*/\n\nfunc TestEncodedLen(t *testing.T) {\n\ttype test struct {\n\t\tn int\n\t\twant int64\n\t}\n\ttests := []test{\n\t\t{0, 0},\n\t\t{1, 2},\n\t\t{2, 4},\n\t\t{3, 5},\n\t\t{4, 7},\n\t\t{5, 8},\n\t\t{6, 10},\n\t\t{7, 12},\n\t\t{10, 16},\n\t\t{11, 18},\n\t}\n\t// check overflow\n\ttests = append(tests, test{(math.MaxInt-4)/8 + 1, 1844674407370955162})\n\ttests = append(tests, test{math.MaxInt/8*5 + 4, math.MaxInt})\n\tfor _, tt := range tests {\n\t\tif got := EncodedLen(tt.n); int64(got) != tt.want {\n\t\t\tt.Errorf(\"EncodedLen(%d): got %d, want %d\", tt.n, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestDecodedLen(t *testing.T) {\n\ttype test struct {\n\t\tn int\n\t\twant int64\n\t}\n\ttests := []test{\n\t\t{0, 0},\n\t\t{2, 1},\n\t\t{4, 2},\n\t\t{5, 3},\n\t\t{7, 4},\n\t\t{8, 5},\n\t\t{10, 6},\n\t\t{12, 7},\n\t\t{16, 10},\n\t\t{18, 11},\n\t}\n\t// check overflow\n\ttests = append(tests, test{math.MaxInt/5 + 1, 1152921504606846976})\n\ttests = append(tests, test{math.MaxInt, 5764607523034234879})\n\tfor _, tt := range tests {\n\t\tif got := DecodedLen(tt.n); int64(got) != tt.want {\n\t\t\tt.Errorf(\"DecodedLen(%d): got %d, want %d\", tt.n, got, tt.want)\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "context", + "path": "gno.land/p/demo/context", + "files": [ + { + "name": "context.gno", + "body": "// Package context provides a minimal implementation of Go context with support\n// for Value and WithValue.\n//\n// Adapted from https://github.com/golang/go/tree/master/src/context/.\n// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\npackage context\n\ntype Context interface {\n\t// Value returns the value associated with this context for key, or nil\n\t// if no value is associated with key.\n\tValue(key interface{}) interface{}\n}\n\n// Empty returns a non-nil, empty context, similar with context.Background and\n// context.TODO in Go.\nfunc Empty() Context {\n\treturn \u0026emptyCtx{}\n}\n\ntype emptyCtx struct{}\n\nfunc (ctx emptyCtx) Value(key interface{}) interface{} {\n\treturn nil\n}\n\nfunc (ctx emptyCtx) String() string {\n\treturn \"context.Empty\"\n}\n\ntype valueCtx struct {\n\tparent Context\n\tkey, val interface{}\n}\n\nfunc (ctx *valueCtx) Value(key interface{}) interface{} {\n\tif ctx.key == key {\n\t\treturn ctx.val\n\t}\n\treturn ctx.parent.Value(key)\n}\n\nfunc stringify(v interface{}) string {\n\tswitch s := v.(type) {\n\tcase stringer:\n\t\treturn s.String()\n\tcase string:\n\t\treturn s\n\t}\n\treturn \"non-stringer\"\n}\n\ntype stringer interface {\n\tString() string\n}\n\nfunc (c *valueCtx) String() string {\n\treturn stringify(c.parent) + \".WithValue(\" +\n\t\tstringify(c.key) + \", \" +\n\t\tstringify(c.val) + \")\"\n}\n\n// WithValue returns a copy of parent in which the value associated with key is\n// val.\nfunc WithValue(parent Context, key, val interface{}) Context {\n\tif key == nil {\n\t\tpanic(\"nil key\")\n\t}\n\t// XXX: if !reflect.TypeOf(key).Comparable() { panic(\"key is not comparable\") }\n\treturn \u0026valueCtx{parent, key, val}\n}\n" + }, + { + "name": "context_test.gno", + "body": "package context\n\nimport \"testing\"\n\nfunc TestContextExample(t *testing.T) {\n\ttype favContextKey string\n\n\tk := favContextKey(\"language\")\n\tctx := WithValue(Empty(), k, \"Gno\")\n\n\tif v := ctx.Value(k); v != nil {\n\t\tif string(v) != \"Gno\" {\n\t\t\tt.Errorf(\"language value should be Gno, but is %s\", v)\n\t\t}\n\t} else {\n\t\tt.Errorf(\"language key value was not found\")\n\t}\n\n\tif v := ctx.Value(favContextKey(\"color\")); v != nil {\n\t\tt.Errorf(\"color key was found\")\n\t}\n}\n\n// otherContext is a Context that's not one of the types defined in context.go.\n// This lets us test code paths that differ based on the underlying type of the\n// Context.\ntype otherContext struct {\n\tContext\n}\n\ntype (\n\tkey1 int\n\tkey2 int\n)\n\n// func (k key2) String() string { return fmt.Sprintf(\"%[1]T(%[1]d)\", k) }\n\nvar (\n\tk1 = key1(1)\n\tk2 = key2(1) // same int as k1, different type\n\tk3 = key2(3) // same type as k2, different int\n)\n\nfunc TestValues(t *testing.T) {\n\tcheck := func(c Context, nm, v1, v2, v3 string) {\n\t\tif v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {\n\t\t\tt.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)\n\t\t}\n\t\tif v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {\n\t\t\tt.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)\n\t\t}\n\t\tif v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {\n\t\t\tt.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)\n\t\t}\n\t}\n\n\tc0 := Empty()\n\tcheck(c0, \"c0\", \"\", \"\", \"\")\n\n\tt.Skip() // XXX: depends on https://github.com/gnolang/gno/issues/2386\n\n\tc1 := WithValue(Empty(), k1, \"c1k1\")\n\tcheck(c1, \"c1\", \"c1k1\", \"\", \"\")\n\n\t/*if got, want := c1.String(), `context.Empty.WithValue(context_test.key1, c1k1)`; got != want {\n\t\tt.Errorf(\"c.String() = %q want %q\", got, want)\n\t}*/\n\n\tc2 := WithValue(c1, k2, \"c2k2\")\n\tcheck(c2, \"c2\", \"c1k1\", \"c2k2\", \"\")\n\n\t/*if got, want := fmt.Sprint(c2), `context.Empty.WithValue(context_test.key1, c1k1).WithValue(context_test.key2(1), c2k2)`; got != want {\n\t\tt.Errorf(\"c.String() = %q want %q\", got, want)\n\t}*/\n\n\tc3 := WithValue(c2, k3, \"c3k3\")\n\tcheck(c3, \"c2\", \"c1k1\", \"c2k2\", \"c3k3\")\n\n\tc4 := WithValue(c3, k1, nil)\n\tcheck(c4, \"c4\", \"\", \"c2k2\", \"c3k3\")\n\n\to0 := otherContext{Empty()}\n\tcheck(o0, \"o0\", \"\", \"\", \"\")\n\n\to1 := otherContext{WithValue(Empty(), k1, \"c1k1\")}\n\tcheck(o1, \"o1\", \"c1k1\", \"\", \"\")\n\n\to2 := WithValue(o1, k2, \"o2k2\")\n\tcheck(o2, \"o2\", \"c1k1\", \"o2k2\", \"\")\n\n\to3 := otherContext{c4}\n\tcheck(o3, \"o3\", \"\", \"c2k2\", \"c3k3\")\n\n\to4 := WithValue(o3, k3, nil)\n\tcheck(o4, \"o4\", \"\", \"c2k2\", \"\")\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "dom", + "path": "gno.land/p/demo/dom", + "files": [ + { + "name": "dom.gno", + "body": "// XXX This is only used for testing in ./tests.\n// Otherwise this package is deprecated.\n// TODO: replace with a package that is supported, and delete this.\n\npackage dom\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype Plot struct {\n\tName string\n\tPosts avl.Tree // postsCtr -\u003e *Post\n\tPostsCtr int\n}\n\nfunc (plot *Plot) AddPost(title string, body string) {\n\tctr := plot.PostsCtr\n\tplot.PostsCtr++\n\tkey := strconv.Itoa(ctr)\n\tpost := \u0026Post{\n\t\tTitle: title,\n\t\tBody: body,\n\t}\n\tplot.Posts.Set(key, post)\n}\n\nfunc (plot *Plot) String() string {\n\tstr := \"# [plot] \" + plot.Name + \"\\n\"\n\tif plot.Posts.Size() \u003e 0 {\n\t\tplot.Posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tstr += \"\\n\"\n\t\t\tstr += value.(*Post).String()\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\ntype Post struct {\n\tTitle string\n\tBody string\n\tComments avl.Tree\n}\n\nfunc (post *Post) String() string {\n\tstr := \"## \" + post.Title + \"\\n\"\n\tstr += \"\"\n\tstr += post.Body\n\tif post.Comments.Size() \u003e 0 {\n\t\tpost.Comments.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tstr += \"\\n\"\n\t\t\tstr += value.(*Comment).String()\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\ntype Comment struct {\n\tCreator string\n\tBody string\n}\n\nfunc (cmm Comment) String() string {\n\treturn cmm.Body + \" - @\" + cmm.Creator + \"\\n\"\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "flow", + "path": "gno.land/p/demo/flow", + "files": [ + { + "name": "LICENSE", + "body": "https://github.com/mxk/go-flowrate/blob/master/LICENSE\nBSD 3-Clause \"New\" or \"Revised\" License\n\nCopyright (c) 2014 The Go-FlowRate Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n\n * Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the\n distribution.\n\n * Neither the name of the go-flowrate project nor the names of its\n contributors may be used to endorse or promote products derived\n from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, + { + "name": "README.md", + "body": "Data Flow Rate Control\n======================\n\nTo download and install this package run:\n\ngo get github.com/mxk/go-flowrate/flowrate\n\nThe documentation is available at:\n\nhttp://godoc.org/github.com/mxk/go-flowrate/flowrate\n" + }, + { + "name": "flow.gno", + "body": "//\n// Written by Maxim Khitrov (November 2012)\n//\n// XXX modified to disable blocking, time.Sleep().\n\n// Package flow provides the tools for monitoring and limiting the flow rate\n// of an arbitrary data stream.\npackage flow\n\nimport (\n\t\"math\"\n\t// \"sync\"\n\t\"time\"\n)\n\n// Monitor monitors and limits the transfer rate of a data stream.\ntype Monitor struct {\n\t// mu sync.Mutex // Mutex guarding access to all internal fields\n\tactive bool // Flag indicating an active transfer\n\tstart time.Duration // Transfer start time (clock() value)\n\tbytes int64 // Total number of bytes transferred\n\tsamples int64 // Total number of samples taken\n\n\trSample float64 // Most recent transfer rate sample (bytes per second)\n\trEMA float64 // Exponential moving average of rSample\n\trPeak float64 // Peak transfer rate (max of all rSamples)\n\trWindow float64 // rEMA window (seconds)\n\n\tsBytes int64 // Number of bytes transferred since sLast\n\tsLast time.Duration // Most recent sample time (stop time when inactive)\n\tsRate time.Duration // Sampling rate\n\n\ttBytes int64 // Number of bytes expected in the current transfer\n\ttLast time.Duration // Time of the most recent transfer of at least 1 byte\n}\n\n// New creates a new flow control monitor. Instantaneous transfer rate is\n// measured and updated for each sampleRate interval. windowSize determines the\n// weight of each sample in the exponential moving average (EMA) calculation.\n// The exact formulas are:\n//\n//\tsampleTime = currentTime - prevSampleTime\n//\tsampleRate = byteCount / sampleTime\n//\tweight = 1 - exp(-sampleTime/windowSize)\n//\tnewRate = weight*sampleRate + (1-weight)*oldRate\n//\n// The default values for sampleRate and windowSize (if \u003c= 0) are 100ms and 1s,\n// respectively.\nfunc New(sampleRate, windowSize time.Duration) *Monitor {\n\tif sampleRate = clockRound(sampleRate); sampleRate \u003c= 0 {\n\t\tsampleRate = 5 * clockRate\n\t}\n\tif windowSize \u003c= 0 {\n\t\twindowSize = 1 * time.Second\n\t}\n\tnow := clock()\n\treturn \u0026Monitor{\n\t\tactive: true,\n\t\tstart: now,\n\t\trWindow: windowSize.Seconds(),\n\t\tsLast: now,\n\t\tsRate: sampleRate,\n\t\ttLast: now,\n\t}\n}\n\n// Update records the transfer of n bytes and returns n. It should be called\n// after each Read/Write operation, even if n is 0.\nfunc (m *Monitor) Update(n int) int {\n\t// m.mu.Lock()\n\tm.update(n)\n\t// m.mu.Unlock()\n\treturn n\n}\n\n// Hack to set the current rEMA.\nfunc (m *Monitor) SetREMA(rEMA float64) {\n\t// m.mu.Lock()\n\tm.rEMA = rEMA\n\tm.samples++\n\t// m.mu.Unlock()\n}\n\n// IO is a convenience method intended to wrap io.Reader and io.Writer method\n// execution. It calls m.Update(n) and then returns (n, err) unmodified.\nfunc (m *Monitor) IO(n int, err error) (int, error) {\n\treturn m.Update(n), err\n}\n\n// Done marks the transfer as finished and prevents any further updates or\n// limiting. Instantaneous and current transfer rates drop to 0. Update, IO, and\n// Limit methods become NOOPs. It returns the total number of bytes transferred.\nfunc (m *Monitor) Done() int64 {\n\t// m.mu.Lock()\n\tif now := m.update(0); m.sBytes \u003e 0 {\n\t\tm.reset(now)\n\t}\n\tm.active = false\n\tm.tLast = 0\n\tn := m.bytes\n\t// m.mu.Unlock()\n\treturn n\n}\n\n// timeRemLimit is the maximum Status.TimeRem value.\nconst timeRemLimit = 999*time.Hour + 59*time.Minute + 59*time.Second\n\n// Status represents the current Monitor status. All transfer rates are in bytes\n// per second rounded to the nearest byte.\ntype Status struct {\n\tActive bool // Flag indicating an active transfer\n\tStart time.Time // Transfer start time\n\tDuration time.Duration // Time period covered by the statistics\n\tIdle time.Duration // Time since the last transfer of at least 1 byte\n\tBytes int64 // Total number of bytes transferred\n\tSamples int64 // Total number of samples taken\n\tInstRate int64 // Instantaneous transfer rate\n\tCurRate int64 // Current transfer rate (EMA of InstRate)\n\tAvgRate int64 // Average transfer rate (Bytes / Duration)\n\tPeakRate int64 // Maximum instantaneous transfer rate\n\tBytesRem int64 // Number of bytes remaining in the transfer\n\tTimeRem time.Duration // Estimated time to completion\n\tProgress Percent // Overall transfer progress\n}\n\nfunc (s Status) String() string {\n\treturn \"STATUS{}\"\n}\n\n// Status returns current transfer status information. The returned value\n// becomes static after a call to Done.\nfunc (m *Monitor) Status() Status {\n\t// m.mu.Lock()\n\tnow := m.update(0)\n\ts := Status{\n\t\tActive: m.active,\n\t\tStart: clockToTime(m.start),\n\t\tDuration: m.sLast - m.start,\n\t\tIdle: now - m.tLast,\n\t\tBytes: m.bytes,\n\t\tSamples: m.samples,\n\t\tPeakRate: round(m.rPeak),\n\t\tBytesRem: m.tBytes - m.bytes,\n\t\tProgress: percentOf(float64(m.bytes), float64(m.tBytes)),\n\t}\n\tif s.BytesRem \u003c 0 {\n\t\ts.BytesRem = 0\n\t}\n\tif s.Duration \u003e 0 {\n\t\trAvg := float64(s.Bytes) / s.Duration.Seconds()\n\t\ts.AvgRate = round(rAvg)\n\t\tif s.Active {\n\t\t\ts.InstRate = round(m.rSample)\n\t\t\ts.CurRate = round(m.rEMA)\n\t\t\tif s.BytesRem \u003e 0 {\n\t\t\t\tif tRate := 0.8*m.rEMA + 0.2*rAvg; tRate \u003e 0 {\n\t\t\t\t\tns := float64(s.BytesRem) / tRate * 1e9\n\t\t\t\t\tif ns \u003e float64(timeRemLimit) {\n\t\t\t\t\t\tns = float64(timeRemLimit)\n\t\t\t\t\t}\n\t\t\t\t\ts.TimeRem = clockRound(time.Duration(ns))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// m.mu.Unlock()\n\treturn s\n}\n\n// Limit restricts the instantaneous (per-sample) data flow to rate bytes per\n// second. It returns the maximum number of bytes (0 \u003c= n \u003c= want) that may be\n// transferred immediately without exceeding the limit. If block == true, the\n// call blocks until n \u003e 0. want is returned unmodified if want \u003c 1, rate \u003c 1,\n// or the transfer is inactive (after a call to Done).\n//\n// At least one byte is always allowed to be transferred in any given sampling\n// period. Thus, if the sampling rate is 100ms, the lowest achievable flow rate\n// is 10 bytes per second.\n//\n// For usage examples, see the implementation of Reader and Writer in io.go.\nfunc (m *Monitor) Limit(want int, rate int64, block bool) (n int) {\n\tif block {\n\t\tpanic(\"blocking not yet supported\")\n\t}\n\tif want \u003c 1 || rate \u003c 1 {\n\t\treturn want\n\t}\n\t// m.mu.Lock()\n\n\t// Determine the maximum number of bytes that can be sent in one sample\n\tlimit := round(float64(rate) * m.sRate.Seconds())\n\tif limit \u003c= 0 {\n\t\tlimit = 1\n\t}\n\n\t_ = m.update(0)\n\t/* XXX\n\t// If block == true, wait until m.sBytes \u003c limit\n\tif now := m.update(0); block {\n\t\tfor m.sBytes \u003e= limit \u0026\u0026 m.active {\n\t\t\tnow = m.waitNextSample(now)\n\t\t}\n\t}\n\t*/\n\n\t// Make limit \u003c= want (unlimited if the transfer is no longer active)\n\tif limit -= m.sBytes; limit \u003e int64(want) || !m.active {\n\t\tlimit = int64(want)\n\t}\n\t// m.mu.Unlock()\n\n\tif limit \u003c 0 {\n\t\tlimit = 0\n\t}\n\treturn int(limit)\n}\n\n// SetTransferSize specifies the total size of the data transfer, which allows\n// the Monitor to calculate the overall progress and time to completion.\nfunc (m *Monitor) SetTransferSize(bytes int64) {\n\tif bytes \u003c 0 {\n\t\tbytes = 0\n\t}\n\t// m.mu.Lock()\n\tm.tBytes = bytes\n\t// m.mu.Unlock()\n}\n\n// update accumulates the transferred byte count for the current sample until\n// clock() - m.sLast \u003e= m.sRate. The monitor status is updated once the current\n// sample is done.\nfunc (m *Monitor) update(n int) (now time.Duration) {\n\tif !m.active {\n\t\treturn\n\t}\n\tif now = clock(); n \u003e 0 {\n\t\tm.tLast = now\n\t}\n\tm.sBytes += int64(n)\n\tif sTime := now - m.sLast; sTime \u003e= m.sRate {\n\t\tt := sTime.Seconds()\n\t\tif m.rSample = float64(m.sBytes) / t; m.rSample \u003e m.rPeak {\n\t\t\tm.rPeak = m.rSample\n\t\t}\n\n\t\t// Exponential moving average using a method similar to *nix load\n\t\t// average calculation. Longer sampling periods carry greater weight.\n\t\tif m.samples \u003e 0 {\n\t\t\tw := math.Exp(-t / m.rWindow)\n\t\t\tm.rEMA = m.rSample + w*(m.rEMA-m.rSample)\n\t\t} else {\n\t\t\tm.rEMA = m.rSample\n\t\t}\n\t\tm.reset(now)\n\t}\n\treturn\n}\n\n// reset clears the current sample state in preparation for the next sample.\nfunc (m *Monitor) reset(sampleTime time.Duration) {\n\tm.bytes += m.sBytes\n\tm.samples++\n\tm.sBytes = 0\n\tm.sLast = sampleTime\n}\n\n/*\n// waitNextSample sleeps for the remainder of the current sample. The lock is\n// released and reacquired during the actual sleep period, so it's possible for\n// the transfer to be inactive when this method returns.\nfunc (m *Monitor) waitNextSample(now time.Duration) time.Duration {\n\tconst minWait = 5 * time.Millisecond\n\tcurrent := m.sLast\n\n\t// sleep until the last sample time changes (ideally, just one iteration)\n\tfor m.sLast == current \u0026\u0026 m.active {\n\t\td := current + m.sRate - now\n\t\t// m.mu.Unlock()\n\t\tif d \u003c minWait {\n\t\t\td = minWait\n\t\t}\n\t\ttime.Sleep(d)\n\t\t// m.mu.Lock()\n\t\tnow = m.update(0)\n\t}\n\treturn now\n}\n*/\n" + }, + { + "name": "io.gno", + "body": "//\n// Written by Maxim Khitrov (November 2012)\n//\n\npackage flow\n\nimport (\n\t\"errors\"\n\t\"io\"\n)\n\n// ErrLimit is returned by the Writer when a non-blocking write is short due to\n// the transfer rate limit.\nvar ErrLimit = errors.New(\"flowrate: flow rate limit exceeded\")\n\n// Limiter is implemented by the Reader and Writer to provide a consistent\n// interface for monitoring and controlling data transfer.\ntype Limiter interface {\n\tDone() int64\n\tStatus() Status\n\tSetTransferSize(bytes int64)\n\tSetLimit(new int64) (old int64)\n\tSetBlocking(new bool) (old bool)\n}\n\n// Reader implements io.ReadCloser with a restriction on the rate of data\n// transfer.\ntype Reader struct {\n\tio.Reader // Data source\n\t*Monitor // Flow control monitor\n\n\tlimit int64 // Rate limit in bytes per second (unlimited when \u003c= 0)\n\tblock bool // What to do when no new bytes can be read due to the limit\n}\n\n// NewReader restricts all Read operations on r to limit bytes per second.\nfunc NewReader(r io.Reader, limit int64) *Reader {\n\treturn \u0026Reader{r, New(0, 0), limit, false} // XXX default false\n}\n\n// Read reads up to len(p) bytes into p without exceeding the current transfer\n// rate limit. It returns (0, nil) immediately if r is non-blocking and no new\n// bytes can be read at this time.\nfunc (r *Reader) Read(p []byte) (n int, err error) {\n\tp = p[:r.Limit(len(p), r.limit, r.block)]\n\tif len(p) \u003e 0 {\n\t\tn, err = r.IO(r.Reader.Read(p))\n\t}\n\treturn\n}\n\n// SetLimit changes the transfer rate limit to new bytes per second and returns\n// the previous setting.\nfunc (r *Reader) SetLimit(new int64) (old int64) {\n\told, r.limit = r.limit, new\n\treturn\n}\n\n// SetBlocking changes the blocking behavior and returns the previous setting. A\n// Read call on a non-blocking reader returns immediately if no additional bytes\n// may be read at this time due to the rate limit.\nfunc (r *Reader) SetBlocking(new bool) (old bool) {\n\tif new == true {\n\t\tpanic(\"blocking not yet supported\")\n\t}\n\told, r.block = r.block, new\n\treturn\n}\n\n// Close closes the underlying reader if it implements the io.Closer interface.\nfunc (r *Reader) Close() error {\n\tdefer r.Done()\n\tif c, ok := r.Reader.(io.Closer); ok {\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n\n// Writer implements io.WriteCloser with a restriction on the rate of data\n// transfer.\ntype Writer struct {\n\tio.Writer // Data destination\n\t*Monitor // Flow control monitor\n\n\tlimit int64 // Rate limit in bytes per second (unlimited when \u003c= 0)\n\tblock bool // What to do when no new bytes can be written due to the limit\n}\n\n// NewWriter restricts all Write operations on w to limit bytes per second. The\n// transfer rate and the default blocking behavior (true) can be changed\n// directly on the returned *Writer.\nfunc NewWriter(w io.Writer, limit int64) *Writer {\n\treturn \u0026Writer{w, New(0, 0), limit, false} // XXX default false\n}\n\n// Write writes len(p) bytes from p to the underlying data stream without\n// exceeding the current transfer rate limit. It returns (n, ErrLimit) if w is\n// non-blocking and no additional bytes can be written at this time.\nfunc (w *Writer) Write(p []byte) (n int, err error) {\n\tvar c int\n\tfor len(p) \u003e 0 \u0026\u0026 err == nil {\n\t\ts := p[:w.Limit(len(p), w.limit, w.block)]\n\t\tif len(s) \u003e 0 {\n\t\t\tc, err = w.IO(w.Writer.Write(s))\n\t\t} else {\n\t\t\treturn n, ErrLimit\n\t\t}\n\t\tp = p[c:]\n\t\tn += c\n\t}\n\treturn\n}\n\n// SetLimit changes the transfer rate limit to new bytes per second and returns\n// the previous setting.\nfunc (w *Writer) SetLimit(new int64) (old int64) {\n\told, w.limit = w.limit, new\n\treturn\n}\n\n// SetBlocking changes the blocking behavior and returns the previous setting. A\n// Write call on a non-blocking writer returns as soon as no additional bytes\n// may be written at this time due to the rate limit.\nfunc (w *Writer) SetBlocking(new bool) (old bool) {\n\told, w.block = w.block, new\n\treturn\n}\n\n// Close closes the underlying writer if it implements the io.Closer interface.\nfunc (w *Writer) Close() error {\n\tdefer w.Done()\n\tif c, ok := w.Writer.(io.Closer); ok {\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n" + }, + { + "name": "io_test.gno", + "body": "//\n// Written by Maxim Khitrov (November 2012)\n//\n\npackage flow\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"time\"\n\n\tios_test \"internal/os_test\"\n)\n\n// XXX ugh, I can't even sleep milliseconds.\n// XXX\n\nconst (\n\t_50ms = 50 * time.Millisecond\n\t_100ms = 100 * time.Millisecond\n\t_200ms = 200 * time.Millisecond\n\t_300ms = 300 * time.Millisecond\n\t_400ms = 400 * time.Millisecond\n\t_500ms = 500 * time.Millisecond\n)\n\nfunc nextStatus(m *Monitor) Status {\n\tsamples := m.samples\n\tfor i := 0; i \u003c 30; i++ {\n\t\tif s := m.Status(); s.Samples != samples {\n\t\t\treturn s\n\t\t}\n\t\tios_test.Sleep(5 * time.Millisecond)\n\t}\n\treturn m.Status()\n}\n\nfunc TestReader(t *testing.T) {\n\tin := make([]byte, 100)\n\tfor i := range in {\n\t\tin[i] = byte(i)\n\t}\n\tb := make([]byte, 100)\n\tr := NewReader(bytes.NewReader(in), 100)\n\tstart := time.Now()\n\n\t// Make sure r implements Limiter\n\t_ = Limiter(r)\n\n\t// 1st read of 10 bytes is performed immediately\n\tif n, err := r.Read(b); n != 10 {\n\t\tt.Fatalf(\"r.Read(b) expected 10 (\u003cnil\u003e); got %v\", n)\n\t} else if err != nil {\n\t\tt.Fatalf(\"r.Read(b) expected 10 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003e _50ms {\n\t\tt.Fatalf(\"r.Read(b) took too long (%v)\", rt.String())\n\t}\n\n\t// No new Reads allowed in the current sample\n\tr.SetBlocking(false)\n\tif n, err := r.Read(b); n != 0 {\n\t\tt.Fatalf(\"r.Read(b) expected 0 (\u003cnil\u003e); got %v\", n)\n\t} else if err != nil {\n\t\tt.Fatalf(\"r.Read(b) expected 0 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003e _50ms {\n\t\tt.Fatalf(\"r.Read(b) took too long (%v)\", rt.String())\n\t}\n\n\tstatus := [6]Status{0: r.Status()} // No samples in the first status\n\n\t// 2nd read of 10 bytes blocks until the next sample\n\t// r.SetBlocking(true)\n\tios_test.Sleep(100 * time.Millisecond)\n\tif n, err := r.Read(b[10:]); n != 10 {\n\t\tt.Fatalf(\"r.Read(b[10:]) expected 10 (\u003cnil\u003e); got %v\", n)\n\t} else if err != nil {\n\t\tt.Fatalf(\"r.Read(b[10:]) expected 10 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003c _100ms {\n\t\tt.Fatalf(\"r.Read(b[10:]) returned ahead of time (%v)\", rt.String())\n\t}\n\n\tstatus[1] = r.Status() // 1st sample\n\tstatus[2] = nextStatus(r.Monitor) // 2nd sample\n\tstatus[3] = nextStatus(r.Monitor) // No activity for the 3rd sample\n\n\tif n := r.Done(); n != 20 {\n\t\tt.Fatalf(\"r.Done() expected 20; got %v\", n)\n\t}\n\n\tstatus[4] = r.Status()\n\tstatus[5] = nextStatus(r.Monitor) // Timeout\n\tstart = status[0].Start\n\n\t// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress\n\twant := []Status{\n\t\t{true, start, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t{true, start, _100ms, 0, 10, 1, 100, 100, 100, 100, 0, 0, 0},\n\t\t{true, start, _200ms, _100ms, 20, 2, 100, 100, 100, 100, 0, 0, 0},\n\t\t{true, start, _300ms, _200ms, 20, 3, 0, 90, 67, 100, 0, 0, 0},\n\t\t{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},\n\t\t{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},\n\t}\n\tfor i, s := range status {\n\t\t// XXX s := s\n\t\tif !statusesAreEqual(\u0026s, \u0026want[i]) {\n\t\t\tt.Errorf(\"r.Status(%v)\\nexpected: %v\\ngot : %v\", i, want[i].String(), s.String())\n\t\t}\n\t}\n\tif !bytes.Equal(b[:20], in[:20]) {\n\t\tt.Errorf(\"r.Read() input doesn't match output\")\n\t}\n}\n\n// XXX blocking writer test doesn't work.\nfunc _TestWriter(t *testing.T) {\n\tb := make([]byte, 100)\n\tfor i := range b {\n\t\tb[i] = byte(i)\n\t}\n\tw := NewWriter(\u0026bytes.Buffer{}, 200)\n\tstart := time.Now()\n\n\t// Make sure w implements Limiter\n\t_ = Limiter(w)\n\n\t// Non-blocking 20-byte write for the first sample returns ErrLimit\n\tw.SetBlocking(false)\n\tif n, err := w.Write(b); n != 20 || err != ErrLimit {\n\t\tt.Fatalf(\"w.Write(b) expected 20 (ErrLimit); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003e _50ms {\n\t\tt.Fatalf(\"w.Write(b) took too long (%v)\", rt)\n\t}\n\n\t// Blocking 80-byte write\n\t// w.SetBlocking(true)\n\t// XXX This test doesn't work, because w.Write calls w.Limit(block=false),\n\t// XXX and it returns ErrLimit after 20. What we want is to keep waiting until 80 is returned,\n\t// XXX but blocking isn't supported. Sleeping 800 shouldn't be sufficient either (its a burst).\n\t// XXX This limits the usage of Limiter and m.Limit().\n\tios_test.Sleep(800 * time.Millisecond)\n\tif n, err := w.Write(b[20:]); n \u003c 80 {\n\t} else if n != 80 || err != nil {\n\t\tt.Fatalf(\"w.Write(b[20:]) expected 80 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003c _300ms {\n\t\t// Explanation for `rt \u003c _300ms` (as opposed to `\u003c _400ms`)\n\t\t//\n\t\t// |\u003c-- start | |\n\t\t// epochs: -----0ms|---100ms|---200ms|---300ms|---400ms\n\t\t// sends: 20|20 |20 |20 |20#\n\t\t//\n\t\t// NOTE: The '#' symbol can thus happen before 400ms is up.\n\t\t// Thus, we can only panic if rt \u003c _300ms.\n\t\tt.Fatalf(\"w.Write(b[20:]) returned ahead of time (%v)\", rt.String())\n\t}\n\n\tw.SetTransferSize(100)\n\tstatus := []Status{w.Status(), nextStatus(w.Monitor)}\n\tstart = status[0].Start\n\n\t// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress\n\twant := []Status{\n\t\t{true, start, _400ms, 0, 80, 4, 200, 200, 200, 200, 20, _100ms, 80000},\n\t\t{true, start, _500ms, _100ms, 100, 5, 200, 200, 200, 200, 0, 0, 100000},\n\t}\n\tfor i, s := range status {\n\t\t// XXX s := s\n\t\tif !statusesAreEqual(\u0026s, \u0026want[i]) {\n\t\t\tt.Errorf(\"w.Status(%v)\\nexpected: %v\\ngot : %v\\n\", i, want[i].String(), s.String())\n\t\t}\n\t}\n\tif !bytes.Equal(b, w.Writer.(*bytes.Buffer).Bytes()) {\n\t\tt.Errorf(\"w.Write() input doesn't match output\")\n\t}\n}\n\nconst (\n\tmaxDeviationForDuration = 50 * time.Millisecond\n\tmaxDeviationForRate int64 = 50\n)\n\n// statusesAreEqual returns true if s1 is equal to s2. Equality here means\n// general equality of fields except for the duration and rates, which can\n// drift due to unpredictable delays (e.g. thread wakes up 25ms after\n// `time.Sleep` has ended).\nfunc statusesAreEqual(s1 *Status, s2 *Status) bool {\n\tif s1.Active == s2.Active \u0026\u0026\n\t\ts1.Start == s2.Start \u0026\u0026\n\t\tdurationsAreEqual(s1.Duration, s2.Duration, maxDeviationForDuration) \u0026\u0026\n\t\ts1.Idle == s2.Idle \u0026\u0026\n\t\ts1.Bytes == s2.Bytes \u0026\u0026\n\t\ts1.Samples == s2.Samples \u0026\u0026\n\t\tratesAreEqual(s1.InstRate, s2.InstRate, maxDeviationForRate) \u0026\u0026\n\t\tratesAreEqual(s1.CurRate, s2.CurRate, maxDeviationForRate) \u0026\u0026\n\t\tratesAreEqual(s1.AvgRate, s2.AvgRate, maxDeviationForRate) \u0026\u0026\n\t\tratesAreEqual(s1.PeakRate, s2.PeakRate, maxDeviationForRate) \u0026\u0026\n\t\ts1.BytesRem == s2.BytesRem \u0026\u0026\n\t\tdurationsAreEqual(s1.TimeRem, s2.TimeRem, maxDeviationForDuration) \u0026\u0026\n\t\ts1.Progress == s2.Progress {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc durationsAreEqual(d1 time.Duration, d2 time.Duration, maxDeviation time.Duration) bool {\n\treturn d2-d1 \u003c= maxDeviation\n}\n\nfunc ratesAreEqual(r1 int64, r2 int64, maxDeviation int64) bool {\n\tsub := r1 - r2\n\tif sub \u003c 0 {\n\t\tsub = -sub\n\t}\n\tif sub \u003c= maxDeviation {\n\t\treturn true\n\t}\n\treturn false\n}\n" + }, + { + "name": "util.gno", + "body": "//\n// Written by Maxim Khitrov (November 2012)\n//\n\npackage flow\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// clockRate is the resolution and precision of clock().\nconst clockRate = 20 * time.Millisecond\n\n// czero is the process start time rounded down to the nearest clockRate\n// increment.\nvar czero = time.Now().Round(clockRate)\n\n// clock returns a low resolution timestamp relative to the process start time.\nfunc clock() time.Duration {\n\treturn time.Now().Round(clockRate).Sub(czero)\n}\n\n// clockToTime converts a clock() timestamp to an absolute time.Time value.\nfunc clockToTime(c time.Duration) time.Time {\n\treturn czero.Add(c)\n}\n\n// clockRound returns d rounded to the nearest clockRate increment.\nfunc clockRound(d time.Duration) time.Duration {\n\treturn (d + clockRate\u003e\u003e1) / clockRate * clockRate\n}\n\n// round returns x rounded to the nearest int64 (non-negative values only).\nfunc round(x float64) int64 {\n\tif _, frac := math.Modf(x); frac \u003e= 0.5 {\n\t\treturn int64(math.Ceil(x))\n\t}\n\treturn int64(math.Floor(x))\n}\n\n// Percent represents a percentage in increments of 1/1000th of a percent.\ntype Percent uint32\n\n// percentOf calculates what percent of the total is x.\nfunc percentOf(x, total float64) Percent {\n\tif x \u003c 0 || total \u003c= 0 {\n\t\treturn 0\n\t} else if p := round(x / total * 1e5); p \u003c= math.MaxUint32 {\n\t\treturn Percent(p)\n\t}\n\treturn Percent(math.MaxUint32)\n}\n\nfunc (p Percent) Float() float64 {\n\treturn float64(p) * 1e-3\n}\n\nfunc (p Percent) String() string {\n\tvar buf [12]byte\n\tb := strconv.AppendUint(buf[:0], uint64(p)/1000, 10)\n\tn := len(b)\n\tb = strconv.AppendUint(b, 1000+uint64(p)%1000, 10)\n\tb[n] = '.'\n\treturn string(append(b, '%'))\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "gnode", + "path": "gno.land/p/demo/gnode", + "files": [ + { + "name": "gnode.gno", + "body": "package gnode\n\n// XXX what about Gnodes signing on behalf of others?\n// XXX like a multi-sig of Gnodes?\n\ntype Name string\n\ntype Gnode interface {\n\t//----------------------------------------\n\t// Basic properties\n\tGetName() Name\n\n\t//----------------------------------------\n\t// Affiliate Gnodes\n\tNumAffiliates() int\n\tGetAffiliates(Name) Affiliate\n\tAddAffiliate(Affiliate) error // must be affiliated\n\tRemAffiliate(Name) error // must have become unaffiliated\n\n\t//----------------------------------------\n\t// Signing\n\tNumSignedDocuments() int\n\tGetSignedDocument(idx int) Document\n\tSignDocument(doc Document) (int, error) // index relative to signer\n\n\t//----------------------------------------\n\t// Rendering\n\tRenderLines() []string\n}\n\ntype Affiliate struct {\n\tType string\n\tGnode Gnode\n\tTags []string\n}\n\ntype MyGnode struct {\n\tName\n\t// Owners // voting set, something that gives authority of action.\n\t// Treasury //\n\t// Affiliates //\n\t// Board // discussions\n\t// Data // XXX ?\n}\n\ntype Affiliates []*Affiliate\n\n// Documents are equal if they compare equal.\n// NOTE: requires all fields to be comparable.\ntype Document struct {\n\tAuthors string\n\t// Timestamp\n\t// Body\n\t// Attachments\n}\n\n// ACTIONS\n\n// * Lend tokens\n// * Pay tokens\n// * Administrate transferrable and non-transferrable tokens\n// * Sum tokens\n// * Passthrough dependencies\n// * Code\n// * ...\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "agent", + "path": "gno.land/p/demo/gnorkle/agent", + "files": [ + { + "name": "whitelist.gno", + "body": "package agent\n\nimport \"gno.land/p/demo/avl\"\n\n// Whitelist manages whitelisted agent addresses.\ntype Whitelist struct {\n\tstore *avl.Tree\n}\n\n// ClearAddresses removes all addresses from the whitelist and puts into a state\n// that indicates it is moot and has no whitelist defined.\nfunc (m *Whitelist) ClearAddresses() {\n\tm.store = nil\n}\n\n// AddAddresses adds the given addresses to the whitelist.\nfunc (m *Whitelist) AddAddresses(addresses []string) {\n\tif m.store == nil {\n\t\tm.store = avl.NewTree()\n\t}\n\n\tfor _, address := range addresses {\n\t\tm.store.Set(address, struct{}{})\n\t}\n}\n\n// RemoveAddress removes the given address from the whitelist if it exists.\nfunc (m *Whitelist) RemoveAddress(address string) {\n\tif m.store == nil {\n\t\treturn\n\t}\n\n\tm.store.Remove(address)\n}\n\n// HasDefinition returns true if the whitelist has a definition. It retuns false if\n// `ClearAddresses` has been called without any subsequent `AddAddresses` calls, or\n// if `AddAddresses` has never been called.\nfunc (m Whitelist) HasDefinition() bool {\n\treturn m.store != nil\n}\n\n// HasAddress returns true if the given address is in the whitelist.\nfunc (m Whitelist) HasAddress(address string) bool {\n\tif m.store == nil {\n\t\treturn false\n\t}\n\n\treturn m.store.Has(address)\n}\n" + }, + { + "name": "whitelist_test.gno", + "body": "package agent_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/agent\"\n)\n\nfunc TestWhitelist(t *testing.T) {\n\tvar whitelist agent.Whitelist\n\n\tif whitelist.HasDefinition() {\n\t\tt.Error(\"whitelist should not be defined initially\")\n\t}\n\n\twhitelist.AddAddresses([]string{\"a\", \"b\"})\n\tif !whitelist.HasAddress(\"a\") {\n\t\tt.Error(`whitelist should have address \"a\"`)\n\t}\n\tif !whitelist.HasAddress(\"b\") {\n\t\tt.Error(`whitelist should have address \"b\"`)\n\t}\n\n\tif !whitelist.HasDefinition() {\n\t\tt.Error(\"whitelist should be defined after adding addresses\")\n\t}\n\n\twhitelist.RemoveAddress(\"a\")\n\tif whitelist.HasAddress(\"a\") {\n\t\tt.Error(`whitelist should not have address \"a\"`)\n\t}\n\tif !whitelist.HasAddress(\"b\") {\n\t\tt.Error(`whitelist should still have address \"b\"`)\n\t}\n\n\twhitelist.ClearAddresses()\n\tif whitelist.HasAddress(\"a\") {\n\t\tt.Error(`whitelist cleared; should not have address \"a\"`)\n\t}\n\tif whitelist.HasAddress(\"b\") {\n\t\tt.Error(`whitelist cleared; should still have address \"b\"`)\n\t}\n\n\tif whitelist.HasDefinition() {\n\t\tt.Error(\"whitelist cleared; should not be defined\")\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "feed", + "path": "gno.land/p/demo/gnorkle/feed", + "files": [ + { + "name": "errors.gno", + "body": "package feed\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"undefined feed\")\n" + }, + { + "name": "task.gno", + "body": "package feed\n\n// Task is a unit of work that can be part of a `Feed` definition. Tasks\n// are executed by agents.\ntype Task interface {\n\tMarshalJSON() ([]byte, error)\n}\n" + }, + { + "name": "type.gno", + "body": "package feed\n\n// Type indicates the type of a feed.\ntype Type int\n\nconst (\n\t// TypeStatic indicates a feed cannot be changed once the first value is committed.\n\tTypeStatic Type = iota\n\t// TypeContinuous indicates a feed can continuously ingest values and will publish\n\t// a new value on request using the values it has ingested.\n\tTypeContinuous\n\t// TypePeriodic indicates a feed can accept one or more values within a certain period\n\t// and will proceed to commit these values at the end up each period to produce an\n\t// aggregate value before starting a new period.\n\tTypePeriodic\n)\n" + }, + { + "name": "value.gno", + "body": "package feed\n\nimport \"time\"\n\n// Value represents a value published by a feed. The `Time` is when the value was published.\ntype Value struct {\n\tString string\n\tTime time.Time\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ingester", + "path": "gno.land/p/demo/gnorkle/ingester", + "files": [ + { + "name": "errors.gno", + "body": "package ingester\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"ingester undefined\")\n" + }, + { + "name": "type.gno", + "body": "package ingester\n\n// Type indicates an ingester type.\ntype Type int\n\nconst (\n\t// TypeSingle indicates an ingester that can only ingest a single within a given period or no period.\n\tTypeSingle Type = iota\n\t// TypeMulti indicates an ingester that can ingest multiple within a given period or no period\n\tTypeMulti\n)\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "message", + "path": "gno.land/p/demo/gnorkle/message", + "files": [ + { + "name": "parse.gno", + "body": "package message\n\nimport \"strings\"\n\n// ParseFunc parses a raw message and returns the message function\n// type extracted from the remainder of the message.\nfunc ParseFunc(rawMsg string) (FuncType, string) {\n\tfuncType, remainder := parseFirstToken(rawMsg)\n\treturn FuncType(funcType), remainder\n}\n\n// ParseID parses a raw message and returns the ID extracted from\n// the remainder of the message.\nfunc ParseID(rawMsg string) (string, string) {\n\treturn parseFirstToken(rawMsg)\n}\n\nfunc parseFirstToken(rawMsg string) (string, string) {\n\tmsgParts := strings.SplitN(rawMsg, \",\", 2)\n\tif len(msgParts) \u003c 2 {\n\t\treturn msgParts[0], \"\"\n\t}\n\n\treturn msgParts[0], msgParts[1]\n}\n" + }, + { + "name": "parse_test.gno", + "body": "package message_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nfunc TestParseFunc(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpFuncType message.FuncType\n\t\texpRemainder string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t},\n\t\t{\n\t\t\tname: \"func only\",\n\t\t\tinput: \"ingest\",\n\t\t\texpFuncType: message.FuncTypeIngest,\n\t\t},\n\t\t{\n\t\t\tname: \"func with short remainder\",\n\t\t\tinput: \"commit,asdf\",\n\t\t\texpFuncType: message.FuncTypeCommit,\n\t\t\texpRemainder: \"asdf\",\n\t\t},\n\t\t{\n\t\t\tname: \"func with long remainder\",\n\t\t\tinput: \"request,hello,world,goodbye\",\n\t\t\texpFuncType: message.FuncTypeRequest,\n\t\t\texpRemainder: \"hello,world,goodbye\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tfuncType, remainder := message.ParseFunc(tt.input)\n\t\t\tif funcType != tt.expFuncType {\n\t\t\t\tt.Errorf(\"expected func type %s, got %s\", tt.expFuncType, funcType)\n\t\t\t}\n\n\t\t\tif remainder != tt.expRemainder {\n\t\t\t\tt.Errorf(\"expected remainder of %s, got %s\", tt.expRemainder, remainder)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "type.gno", + "body": "package message\n\n// FuncType is the type of function that is being called by the agent.\ntype FuncType string\n\nconst (\n\t// FuncTypeIngest means the agent is sending data for ingestion.\n\tFuncTypeIngest FuncType = \"ingest\"\n\t// FuncTypeCommit means the agent is requesting a feed commit the transitive data\n\t// being held by its ingester.\n\tFuncTypeCommit FuncType = \"commit\"\n\t// FuncTypeRequest means the agent is requesting feed definitions for all those\n\t// that it is whitelisted to provide data for.\n\tFuncTypeRequest FuncType = \"request\"\n)\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "gnorkle", + "path": "gno.land/p/demo/gnorkle/gnorkle", + "files": [ + { + "name": "feed.gno", + "body": "package gnorkle\n\nimport (\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Feed is an abstraction used by a gnorkle `Instance` to ingest data from\n// agents and provide data feeds to consumers.\ntype Feed interface {\n\tID() string\n\tType() feed.Type\n\tValue() (value feed.Value, dataType string, consumable bool)\n\tIngest(funcType message.FuncType, rawMessage, providerAddress string) error\n\tMarshalJSON() ([]byte, error)\n\tTasks() []feed.Task\n\tIsActive() bool\n}\n\n// FeedWithWhitelist associates a `Whitelist` with a `Feed`.\ntype FeedWithWhitelist struct {\n\tFeed\n\tWhitelist\n}\n" + }, + { + "name": "ingester.gno", + "body": "package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/ingester\"\n\n// Ingester is the abstraction that allows a `Feed` to ingest data from agents\n// and commit it to storage using zero or more intermediate aggregation steps.\ntype Ingester interface {\n\tType() ingester.Type\n\tIngest(value, providerAddress string) (canAutoCommit bool, err error)\n\tCommitValue(storage Storage, providerAddress string) error\n}\n" + }, + { + "name": "instance.gno", + "body": "package gnorkle\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/agent\"\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Instance is a single instance of an oracle.\ntype Instance struct {\n\tfeeds *avl.Tree\n\twhitelist agent.Whitelist\n}\n\n// NewInstance creates a new instance of an oracle.\nfunc NewInstance() *Instance {\n\treturn \u0026Instance{\n\t\tfeeds: avl.NewTree(),\n\t}\n}\n\nfunc assertValidID(id string) error {\n\tif len(id) == 0 {\n\t\treturn errors.New(\"feed ids cannot be empty\")\n\t}\n\n\tif strings.Contains(id, \",\") {\n\t\treturn errors.New(\"feed ids cannot contain commas\")\n\t}\n\n\treturn nil\n}\n\nfunc (i *Instance) assertFeedDoesNotExist(id string) error {\n\tif i.feeds.Has(id) {\n\t\treturn errors.New(\"feed already exists\")\n\t}\n\n\treturn nil\n}\n\n// AddFeeds adds feeds to the instance with empty whitelists.\nfunc (i *Instance) AddFeeds(feeds ...Feed) error {\n\tfor _, feed := range feeds {\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: new(agent.Whitelist),\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// AddFeedsWithWhitelists adds feeds to the instance with the given whitelists.\nfunc (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) error {\n\tfor _, feed := range feeds {\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: feed.Whitelist,\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// RemoveFeed removes a feed from the instance.\nfunc (i *Instance) RemoveFeed(id string) {\n\ti.feeds.Remove(id)\n}\n\n// PostMessageHandler is a type that allows for post-processing of feed state after a feed\n// ingests a message from an agent.\ntype PostMessageHandler interface {\n\tHandle(i *Instance, funcType message.FuncType, feed Feed) error\n}\n\n// HandleMessage handles a message from an agent and routes to either the logic that returns\n// feed definitions or the logic that allows a feed to ingest a message.\n//\n// TODO: Consider further message types that could allow administrative action such as modifying\n// a feed's whitelist without the owner of this oracle having to maintain a reference to it.\nfunc (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) {\n\tcaller := string(std.GetOrigCaller())\n\n\tfuncType, msg := message.ParseFunc(msg)\n\n\tswitch funcType {\n\tcase message.FuncTypeRequest:\n\t\treturn i.GetFeedDefinitions(caller)\n\n\tdefault:\n\t\tid, msg := message.ParseID(msg)\n\t\tif err := assertValidID(id); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfeedWithWhitelist, err := i.getFeedWithWhitelist(id)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, caller, nil) {\n\t\t\treturn \"\", errors.New(\"caller not whitelisted\")\n\t\t}\n\n\t\tif err := feedWithWhitelist.Ingest(funcType, msg, caller); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif postHandler != nil {\n\t\t\tpostHandler.Handle(i, funcType, feedWithWhitelist)\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (i *Instance) getFeed(id string) (Feed, error) {\n\tuntypedFeed, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeed, ok := untypedFeed.(Feed)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid feed type\")\n\t}\n\n\treturn feed, nil\n}\n\nfunc (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) {\n\tuntypedFeedWithWhitelist, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid feed with whitelist type\")\n\t}\n\n\treturn feedWithWhitelist, nil\n}\n\n// GetFeedValue returns the most recently published value of a feed along with a string\n// representation of the value's type and boolean indicating whether the value is\n// okay for consumption.\nfunc (i *Instance) GetFeedValue(id string) (feed.Value, string, bool, error) {\n\tfoundFeed, err := i.getFeed(id)\n\tif err != nil {\n\t\treturn feed.Value{}, \"\", false, err\n\t}\n\n\tvalue, valueType, consumable := foundFeed.Value()\n\treturn value, valueType, consumable, nil\n}\n\n// GetFeedDefinitions returns a JSON string representing the feed definitions for which the given\n// agent address is whitelisted to provide values for ingestion.\nfunc (i *Instance) GetFeedDefinitions(forAddress string) (string, error) {\n\tinstanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress)\n\n\tbuf := new(strings.Builder)\n\tbuf.WriteString(\"[\")\n\tfirst := true\n\tvar err error\n\n\t// The boolean value returned by this callback function indicates whether to stop iterating.\n\ti.feeds.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tfeedWithWhitelist, ok := value.(FeedWithWhitelist)\n\t\tif !ok {\n\t\t\terr = errors.New(\"invalid feed type\")\n\t\t\treturn true\n\t\t}\n\n\t\t// Don't give agents the ability to try to publish to inactive feeds.\n\t\tif !feedWithWhitelist.IsActive() {\n\t\t\treturn false\n\t\t}\n\n\t\t// Skip feeds the address is not whitelisted for.\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, forAddress, \u0026instanceHasAddressWhitelisted) {\n\t\t\treturn false\n\t\t}\n\n\t\tvar taskBytes []byte\n\t\tif taskBytes, err = feedWithWhitelist.Feed.MarshalJSON(); err != nil {\n\t\t\treturn true\n\t\t}\n\n\t\t// Guard against any tasks that shouldn't be returned; maybe they are not active because they have\n\t\t// already been completed.\n\t\tif len(taskBytes) == 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tif !first {\n\t\t\tbuf.WriteString(\",\")\n\t\t}\n\n\t\tfirst = false\n\t\tbuf.Write(taskBytes)\n\t\treturn true\n\t})\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf.WriteString(\"]\")\n\treturn buf.String(), nil\n}\n" + }, + { + "name": "storage.gno", + "body": "package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/feed\"\n\n// Storage defines how published feed values should be read\n// and written.\ntype Storage interface {\n\tPut(value string) error\n\tGetLatest() feed.Value\n\tGetHistory() []feed.Value\n}\n" + }, + { + "name": "whitelist.gno", + "body": "package gnorkle\n\n// Whitelist is used to manage which agents are allowed to interact.\ntype Whitelist interface {\n\tClearAddresses()\n\tAddAddresses(addresses []string)\n\tRemoveAddress(address string)\n\tHasDefinition() bool\n\tHasAddress(address string) bool\n}\n\n// ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) ClearWhitelist(feedID string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.ClearAddresses()\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.ClearAddresses()\n\treturn nil\n}\n\n// AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) AddToWhitelist(feedID string, addresses []string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.AddAddresses(addresses)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.AddAddresses(addresses)\n\treturn nil\n}\n\n// RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) RemoveFromWhitelist(feedID string, address string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.RemoveAddress(address)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.RemoveAddress(address)\n\treturn nil\n}\n\n// addressWhiteListed returns true if:\n// - the feed has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has no white list.\nfunc addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address string, instanceWhitelistedOverride *bool) bool {\n\t// A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is\n\t// not a part of it. An empty whitelist defers to the instance whitelist.\n\tif feedWhitelist != nil {\n\t\tif feedWhitelist.HasDefinition() \u0026\u0026 !feedWhitelist.HasAddress(address) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Getting to this point means that one of the following is true:\n\t\t// - the feed has no defined whitelist (so it can't possibly have the address whitelisted)\n\t\t// - the feed has a defined whitelist and the caller is a part of it\n\t\t//\n\t\t// In this case, we can be sure that the boolean indicating whether the feed has this address whitelisted\n\t\t// is equivalent to the boolean indicating whether the feed has a defined whitelist.\n\t\tif feedWhitelist.HasDefinition() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif instanceWhitelistedOverride != nil {\n\t\treturn *instanceWhitelistedOverride\n\t}\n\n\t// We were unable able to determine whether this address is allowed after looking at the feed whitelist,\n\t// so fall back to the instance whitelist. A complete absence of values in the instance whitelist means\n\t// that the instance has no whitelist so we can return true because everything is allowed by default.\n\tif instanceWhitelist == nil || !instanceWhitelist.HasDefinition() {\n\t\treturn true\n\t}\n\n\t// The instance whitelist is defined so if the address is present then it is allowed.\n\treturn instanceWhitelist.HasAddress(address)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "storage", + "path": "gno.land/p/demo/gnorkle/storage", + "files": [ + { + "name": "errors.gno", + "body": "package storage\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"undefined storage\")\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "simple", + "path": "gno.land/p/demo/gnorkle/storage/simple", + "files": [ + { + "name": "storage.gno", + "body": "package simple\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/storage\"\n)\n\n// Storage is simple, bounded storage for published feed values.\ntype Storage struct {\n\tvalues []feed.Value\n\tmaxValues uint\n}\n\n// NewStorage creates a new Storage with the given maximum number of values.\n// If maxValues is 0, the storage is bounded to a size of one. If this is not desirable,\n// then don't provide a value of 0.\nfunc NewStorage(maxValues uint) *Storage {\n\tif maxValues == 0 {\n\t\tmaxValues = 1\n\t}\n\n\treturn \u0026Storage{\n\t\tmaxValues: maxValues,\n\t}\n}\n\n// Put adds a new value to the storage. If the storage is full, the oldest value\n// is removed. If maxValues is 0, the storage is bounded to a size of one.\nfunc (s *Storage) Put(value string) error {\n\tif s == nil {\n\t\treturn storage.ErrUndefined\n\t}\n\n\ts.values = append(s.values, feed.Value{String: value, Time: time.Now()})\n\tif uint(len(s.values)) \u003e s.maxValues {\n\t\ts.values = s.values[1:]\n\t}\n\n\treturn nil\n}\n\n// GetLatest returns the most recently added value, or an empty value if none exist.\nfunc (s Storage) GetLatest() feed.Value {\n\tif len(s.values) == 0 {\n\t\treturn feed.Value{}\n\t}\n\n\treturn s.values[len(s.values)-1]\n}\n\n// GetHistory returns all values in the storage, from oldest to newest.\nfunc (s Storage) GetHistory() []feed.Value {\n\treturn s.values\n}\n" + }, + { + "name": "storage_test.gno", + "body": "package simple_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/storage\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n)\n\nfunc TestStorage(t *testing.T) {\n\tvar undefinedStorage *simple.Storage\n\tif err := undefinedStorage.Put(\"\"); err != storage.ErrUndefined {\n\t\tt.Error(\"expected storage.ErrUndefined on undefined storage\")\n\t}\n\n\ttests := []struct {\n\t\tname string\n\t\tvaluesToPut []string\n\t\texpLatestValueString string\n\t\texpLatestValueTimeIsZero bool\n\t\texpHistoricalValueStrings []string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\texpLatestValueTimeIsZero: true,\n\t\t},\n\t\t{\n\t\t\tname: \"one value\",\n\t\t\tvaluesToPut: []string{\"one\"},\n\t\t\texpLatestValueString: \"one\",\n\t\t\texpHistoricalValueStrings: []string{\"one\"},\n\t\t},\n\t\t{\n\t\t\tname: \"two values\",\n\t\t\tvaluesToPut: []string{\"one\", \"two\"},\n\t\t\texpLatestValueString: \"two\",\n\t\t\texpHistoricalValueStrings: []string{\"one\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tname: \"three values\",\n\t\t\tvaluesToPut: []string{\"one\", \"two\", \"three\"},\n\t\t\texpLatestValueString: \"three\",\n\t\t\texpHistoricalValueStrings: []string{\"two\", \"three\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsimpleStorage := simple.NewStorage(2)\n\t\t\tfor _, value := range tt.valuesToPut {\n\t\t\t\tif err := simpleStorage.Put(value); err != nil {\n\t\t\t\t\tt.Fatal(\"unexpected error putting value in storage\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlatestValue := simpleStorage.GetLatest()\n\t\t\tif latestValue.String != tt.expLatestValueString {\n\t\t\t\tt.Errorf(\"expected latest value of %s, got %s\", tt.expLatestValueString, latestValue.String)\n\t\t\t}\n\n\t\t\tif latestValue.Time.IsZero() != tt.expLatestValueTimeIsZero {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"expected latest value time zero value of %t, got %t\",\n\t\t\t\t\ttt.expLatestValueTimeIsZero,\n\t\t\t\t\tlatestValue.Time.IsZero(),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\thistoricalValues := simpleStorage.GetHistory()\n\t\t\tif len(historicalValues) != len(tt.expHistoricalValueStrings) {\n\t\t\t\tt.Fatal(\"historical values length does not match\")\n\t\t\t}\n\n\t\t\tfor i, expValue := range tt.expHistoricalValueStrings {\n\t\t\t\tif expValue != historicalValues[i].String {\n\t\t\t\t\tt.Errorf(\n\t\t\t\t\t\t\"expected historical value at idx %d to be %s, got %s\",\n\t\t\t\t\t\ti,\n\t\t\t\t\t\texpValue,\n\t\t\t\t\t\thistoricalValues[i].String,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tif historicalValues[i].Time.IsZero() {\n\t\t\t\t\tt.Errorf(\"unexpeced zero time for historical value at index %d\", i)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "single", + "path": "gno.land/p/demo/gnorkle/ingesters/single", + "files": [ + { + "name": "ingester.gno", + "body": "package single\n\nimport (\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n)\n\n// ValueIngester is an ingester that ingests a single value.\ntype ValueIngester struct {\n\tvalue string\n}\n\n// Type returns the type of the ingester.\nfunc (i *ValueIngester) Type() ingester.Type {\n\treturn ingester.TypeSingle\n}\n\n// Ingest ingests a value provided by the given agent address.\nfunc (i *ValueIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i == nil {\n\t\treturn false, ingester.ErrUndefined\n\t}\n\n\ti.value = value\n\treturn true, nil\n}\n\n// CommitValue commits the ingested value to the given storage instance.\nfunc (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) error {\n\tif i == nil {\n\t\treturn ingester.ErrUndefined\n\t}\n\n\treturn valueStorer.Put(i.value)\n}\n" + }, + { + "name": "ingester_test.gno", + "body": "package single_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n)\n\nfunc TestValueIngester(t *testing.T) {\n\tstorage := simple.NewStorage(1)\n\n\tvar undefinedIngester *single.ValueIngester\n\tif _, err := undefinedIngester.Ingest(\"asdf\", \"gno11111\"); err != ingester.ErrUndefined {\n\t\tt.Error(\"undefined ingester call to Ingest should return ingester.ErrUndefined\")\n\t}\n\tif err := undefinedIngester.CommitValue(storage, \"gno11111\"); err != ingester.ErrUndefined {\n\t\tt.Error(\"undefined ingester call to CommitValue should return ingester.ErrUndefined\")\n\t}\n\n\tvar valueIngester single.ValueIngester\n\tif typ := valueIngester.Type(); typ != ingester.TypeSingle {\n\t\tt.Error(\"single value ingester should return type ingester.TypeSingle\")\n\t}\n\n\tingestValue := \"value\"\n\tautocommit, err := valueIngester.Ingest(ingestValue, \"gno11111\")\n\tif !autocommit {\n\t\tt.Error(\"single value ingester should return autocommit true\")\n\t}\n\tif err != nil {\n\t\tt.Errorf(\"unexpected ingest error %s\", err.Error())\n\t}\n\n\tif err := valueIngester.CommitValue(storage, \"gno11111\"); err != nil {\n\t\tt.Errorf(\"unexpected commit error %s\", err.Error())\n\t}\n\n\tif latestValue := storage.GetLatest(); latestValue.String != ingestValue {\n\t\tt.Errorf(\"expected latest value of %s, got %s\", ingestValue, latestValue.String)\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "static", + "path": "gno.land/p/demo/gnorkle/feeds/static", + "files": [ + { + "name": "feed.gno", + "body": "package static\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Feed is a static feed.\ntype Feed struct {\n\tid string\n\tisLocked bool\n\tvalueDataType string\n\tingester gnorkle.Ingester\n\tstorage gnorkle.Storage\n\ttasks []feed.Task\n}\n\n// NewFeed creates a new static feed.\nfunc NewFeed(\n\tid string,\n\tvalueDataType string,\n\tingester gnorkle.Ingester,\n\tstorage gnorkle.Storage,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn \u0026Feed{\n\t\tid: id,\n\t\tvalueDataType: valueDataType,\n\t\tingester: ingester,\n\t\tstorage: storage,\n\t\ttasks: tasks,\n\t}\n}\n\n// NewSingleValueFeed is a convenience function for creating a static feed\n// that autocommits a value after a single ingestion.\nfunc NewSingleValueFeed(\n\tid string,\n\tvalueDataType string,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn NewFeed(\n\t\tid,\n\t\tvalueDataType,\n\t\t\u0026single.ValueIngester{},\n\t\tsimple.NewStorage(1),\n\t\ttasks...,\n\t)\n}\n\n// ID returns the feed's ID.\nfunc (f Feed) ID() string {\n\treturn f.id\n}\n\n// Type returns the feed's type.\nfunc (f Feed) Type() feed.Type {\n\treturn feed.TypeStatic\n}\n\n// Ingest ingests a message into the feed. It either adds the value to the ingester's\n// pending values or commits the value to the storage.\nfunc (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error {\n\tif f == nil {\n\t\treturn feed.ErrUndefined\n\t}\n\n\tif f.isLocked {\n\t\treturn errors.New(\"feed locked\")\n\t}\n\n\tswitch funcType {\n\tcase message.FuncTypeIngest:\n\t\t// Autocommit the ingester's value if it's a single value ingester\n\t\t// because this is a static feed and this is the only value it will ever have.\n\t\tif canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit \u0026\u0026 err == nil {\n\t\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tf.isLocked = true\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\tcase message.FuncTypeCommit:\n\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tf.isLocked = true\n\n\tdefault:\n\t\treturn errors.New(\"invalid message function \" + string(funcType))\n\t}\n\n\treturn nil\n}\n\n// Value returns the feed's latest value, it's data type, and whether or not it can\n// be safely consumed. In this case it uses `f.isLocked` because, this being a static\n// feed, it will only ever have one value; once that value is committed the feed is locked\n// and there is a valid, non-empty value to consume.\nfunc (f Feed) Value() (feed.Value, string, bool) {\n\treturn f.storage.GetLatest(), f.valueDataType, f.isLocked\n}\n\n// MarshalJSON marshals the components of the feed that are needed for\n// an agent to execute tasks and send values for ingestion.\nfunc (f Feed) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write([]byte(\n\t\t`{\"id\":\"` + f.id +\n\t\t\t`\",\"type\":\"` + ufmt.Sprintf(\"%d\", int(f.Type())) +\n\t\t\t`\",\"value_type\":\"` + f.valueDataType +\n\t\t\t`\",\"tasks\":[`),\n\t)\n\n\tfirst := true\n\tfor _, task := range f.tasks {\n\t\tif !first {\n\t\t\tw.WriteString(\",\")\n\t\t}\n\n\t\ttaskJSON, err := task.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tw.Write(taskJSON)\n\t\tfirst = false\n\t}\n\n\tw.Write([]byte(\"]}\"))\n\tw.Flush()\n\n\treturn buf.Bytes(), nil\n}\n\n// Tasks returns the feed's tasks. This allows task consumers to extract task\n// contents without having to marshal the entire feed.\nfunc (f Feed) Tasks() []feed.Task {\n\treturn f.tasks\n}\n\n// IsActive returns true if the feed is accepting ingestion requests from agents.\nfunc (f Feed) IsActive() bool {\n\treturn !f.isLocked\n}\n" + }, + { + "name": "feed_test.gno", + "body": "package static_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n)\n\ntype mockIngester struct {\n\tcanAutoCommit bool\n\tingestErr error\n\tcommitErr error\n\tvalue string\n\tproviderAddress string\n}\n\nfunc (i mockIngester) Type() ingester.Type {\n\treturn ingester.Type(0)\n}\n\nfunc (i *mockIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i.ingestErr != nil {\n\t\treturn false, i.ingestErr\n\t}\n\n\ti.value = value\n\ti.providerAddress = providerAddress\n\treturn i.canAutoCommit, nil\n}\n\nfunc (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error {\n\tif i.commitErr != nil {\n\t\treturn i.commitErr\n\t}\n\n\treturn storage.Put(i.value)\n}\n\nfunc TestNewSingleValueFeed(t *testing.T) {\n\tstaticFeed := static.NewSingleValueFeed(\"1\", \"\")\n\tif staticFeed.ID() != \"1\" {\n\t\tt.Errorf(\"expected ID to be 1, got %s\", staticFeed.ID())\n\t}\n\n\tif staticFeed.Type() != feed.TypeStatic {\n\t\tt.Errorf(\"expected static feed type, got %s\", staticFeed.Type())\n\t}\n}\n\nfunc TestFeed_Ingest(t *testing.T) {\n\tvar undefinedFeed *static.Feed\n\tif undefinedFeed.Ingest(\"\", \"\", \"\") != feed.ErrUndefined {\n\t\tt.Errorf(\"expected ErrUndefined, got nil\")\n\t}\n\n\ttests := []struct {\n\t\tname string\n\t\tingester *mockIngester\n\t\tverifyIsLocked bool\n\t\tdoCommit bool\n\t\tfuncType message.FuncType\n\t\tmsg string\n\t\tproviderAddress string\n\t\texpFeedValueString string\n\t\texpErrText string\n\t\texpIsActive bool\n\t}{\n\t\t{\n\t\t\tname: \"func invalid error\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncType(\"derp\"),\n\t\t\texpErrText: \"invalid message function derp\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest ingest error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tingestErr: errors.New(\"ingest error\"),\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"ingest error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func commit commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"only ingest\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest autocommit\",\n\t\t\tingester: \u0026mockIngester{canAutoCommit: true},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpFeedValueString: \"still active feed\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"commit no value\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\tmsg: \"shouldn't be stored\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest then commmit\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"blahblah\",\n\t\t\tdoCommit: true,\n\t\t\texpFeedValueString: \"blahblah\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewFeed(\n\t\t\t\t\"1\",\n\t\t\t\t\"string\",\n\t\t\t\ttt.ingester,\n\t\t\t\tsimple.NewStorage(1),\n\t\t\t\tnil,\n\t\t\t)\n\n\t\t\tvar errText string\n\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\tif errText != tt.expErrText {\n\t\t\t\tt.Fatalf(\"expected error text %s, got %s\", tt.expErrText, errText)\n\t\t\t}\n\n\t\t\tif tt.doCommit {\n\t\t\t\terr := staticFeed.Ingest(message.FuncTypeCommit, \"\", \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"follow up commit failed: %s\", err.Error())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tt.verifyIsLocked {\n\t\t\t\terrText = \"\"\n\t\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\t\terrText = err.Error()\n\t\t\t\t}\n\n\t\t\t\tif errText != \"feed locked\" {\n\t\t\t\t\tt.Fatalf(\"expected error text feed locked, got %s\", errText)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tt.ingester.providerAddress != tt.providerAddress {\n\t\t\t\tt.Errorf(\"expected provider address %s, got %s\", tt.providerAddress, tt.ingester.providerAddress)\n\t\t\t}\n\n\t\t\tfeedValue, dataType, isLocked := staticFeed.Value()\n\t\t\tif feedValue.String != tt.expFeedValueString {\n\t\t\t\tt.Errorf(\"expected feed value string %s, got %s\", tt.expFeedValueString, feedValue.String)\n\t\t\t}\n\n\t\t\tif dataType != \"string\" {\n\t\t\t\tt.Errorf(\"expected data type string, got %s\", dataType)\n\t\t\t}\n\n\t\t\tif isLocked != tt.verifyIsLocked {\n\t\t\t\tt.Errorf(\"expected is locked %t, got %t\", tt.verifyIsLocked, isLocked)\n\t\t\t}\n\n\t\t\tif staticFeed.IsActive() != tt.expIsActive {\n\t\t\t\tt.Errorf(\"expected is active %t, got %t\", tt.expIsActive, staticFeed.IsActive())\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockTask struct {\n\terr error\n\tvalue string\n}\n\nfunc (t mockTask) MarshalJSON() ([]byte, error) {\n\tif t.err != nil {\n\t\treturn nil, t.err\n\t}\n\n\treturn []byte(`{\"value\":\"` + t.value + `\"}`), nil\n}\n\nfunc TestFeed_Tasks(t *testing.T) {\n\tid := \"99\"\n\tvalueDataType := \"int\"\n\n\ttests := []struct {\n\t\tname string\n\t\ttasks []feed.Task\n\t\texpErrText string\n\t\texpJSON string\n\t}{\n\t\t{\n\t\t\tname: \"no tasks\",\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"marshal error\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{err: errors.New(\"marshal error\")},\n\t\t\t},\n\t\t\texpErrText: \"marshal error\",\n\t\t},\n\t\t{\n\t\t\tname: \"one task\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"single\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"single\"}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"two tasks\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"first\"},\n\t\t\t\tmockTask{value: \"second\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"first\"},{\"value\":\"second\"}]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewSingleValueFeed(\n\t\t\t\tid,\n\t\t\t\tvalueDataType,\n\t\t\t\ttt.tasks...,\n\t\t\t)\n\n\t\t\tif len(staticFeed.Tasks()) != len(tt.tasks) {\n\t\t\t\tt.Fatalf(\"expected %d tasks, got %d\", len(tt.tasks), len(staticFeed.Tasks()))\n\t\t\t}\n\n\t\t\tvar errText string\n\t\t\tjson, err := staticFeed.MarshalJSON()\n\t\t\tif err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\tif errText != tt.expErrText {\n\t\t\t\tt.Fatalf(\"expected error text %s, got %s\", tt.expErrText, errText)\n\t\t\t}\n\n\t\t\tif string(json) != tt.expJSON {\n\t\t\t\tt.Errorf(\"expected json %s, got %s\", tt.expJSON, string(json))\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "exts", + "path": "gno.land/p/demo/grc/exts", + "files": [ + { + "name": "token_metadata.gno", + "body": "package exts\n\ntype TokenMetadata interface {\n\t// Returns the name of the token.\n\tGetName() string\n\n\t// Returns the symbol of the token, usually a shorter version of the\n\t// name.\n\tGetSymbol() string\n\n\t// Returns the decimals places of the token.\n\tGetDecimals() uint\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "grc1155", + "path": "gno.land/p/demo/grc/grc1155", + "files": [ + { + "name": "README.md", + "body": "# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155" + }, + { + "name": "basic_grc1155_token.gno", + "body": "package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.GetOrigCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.GetOrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.GetOrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.GetOrigCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.GetOrigCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.GetOrigCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n" + }, + { + "name": "basic_grc1155_token_test.gno", + "body": "package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\treturn t.Errorf(\"should not be nil\")\n\t}\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\turi := dummy.Uri()\n\tif uri != dummyURI {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", dummyURI, uri)\n\t}\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceAddr1OfToken1 != 0 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 0, balanceAddr1OfToken1)\n\t}\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tif balanceAddr1OfToken1 != 10 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 10, balanceAddr1OfToken1)\n\t}\n\tif balanceAddr1OfToken2 != 100 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 100, balanceAddr1OfToken2)\n\t}\n\tif balanceAddr2OfToken1 != 20 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 20, balanceAddr2OfToken1)\n\t}\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tif balanceBatch[0] != 0 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 0, balanceBatch[0])\n\t}\n\tif balanceBatch[1] != 0 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 0, balanceBatch[1])\n\t}\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tif balanceBatch[0] != 10 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 10, balanceBatch[0])\n\t}\n\tif balanceBatch[1] != 20 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 20, balanceBatch[1])\n\t}\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tif isApprovedForAll != false {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", false, isApprovedForAll)\n\t}\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.GetOrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tif isApprovedForAll != false {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", false, isApprovedForAll)\n\t}\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tif isApprovedForAll != true {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", true, isApprovedForAll)\n\t}\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tif isApprovedForAll != false {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", false, isApprovedForAll)\n\t}\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.GetOrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceOfCaller != 40 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 40, balanceOfCaller)\n\t}\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceOfAddr != 60 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 60, balanceOfAddr)\n\t}\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.GetOrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check token1's balance of caller after batch transfer\n\tif balanceBatch[0] != 6 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 6, balanceBatch[0])\n\t}\n\n\t// Check token1's balance of addr after batch transfer\n\tif balanceBatch[1] != 4 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 4, balanceBatch[1])\n\t}\n\n\t// Check token2's balance of caller after batch transfer\n\tif balanceBatch[2] != 40 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 40, balanceBatch[2])\n\t}\n\n\t// Check token2's balance of addr after batch transfer\n\tif balanceBatch[3] != 60 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 60, balanceBatch[3])\n\t}\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\t// Check token1's balance of addr1 after mint\n\tif balanceBatch[0] != 100 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 100, balanceBatch[0])\n\t}\n\t// Check token1's balance of addr2 after mint\n\tif balanceBatch[1] != 50 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 50, balanceBatch[1])\n\t}\n\t// Check token2's balance of addr1 after mint\n\tif balanceBatch[2] != 200 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 200, balanceBatch[2])\n\t}\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\t// Check token1's balance of addr1 after batch mint\n\tif balanceBatch[0] != 100 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 100, balanceBatch[0])\n\t}\n\t// Check token1's balance of addr2 after batch mint\n\tif balanceBatch[1] != 300 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 300, balanceBatch[1])\n\t}\n\t// Check token2's balance of addr1 after batch mint\n\tif balanceBatch[2] != 200 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 200, balanceBatch[2])\n\t}\n\t// Check token2's balance of addr2 after batch mint\n\tif balanceBatch[3] != 400 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 400, balanceBatch[3])\n\t}\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, 60)\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.Burn(addr, tid1, 160)\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.Burn(addr, tid1, 60)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\terr = dummy.Burn(addr, tid2, 60)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check token1's balance of addr after burn\n\tif balanceBatch[0] != 40 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 40, balanceBatch[0])\n\t}\n\t// Check token2's balance of addr after burn\n\tif balanceBatch[1] != 140 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 140, balanceBatch[1])\n\t}\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check token1's balance of addr after batch burn\n\tif balanceBatch[0] != 40 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 40, balanceBatch[0])\n\t}\n\t// Check token2's balance of addr after batch burn\n\tif balanceBatch[1] != 140 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 140, balanceBatch[1])\n\t}\n}\n" + }, + { + "name": "errors.gno", + "body": "package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n" + }, + { + "name": "igrc1155.gno", + "body": "package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n" + }, + { + "name": "util.gno", + "body": "package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "uassert", + "path": "gno.land/p/demo/uassert", + "files": [ + { + "name": "doc.gno", + "body": "package uassert // import \"gno.land/p/demo/uassert\"\n" + }, + { + "name": "helpers.gno", + "body": "package uassert\n\nimport \"strings\"\n\nfunc fail(t TestingT, customMsgs []string, failureMessage string, args ...interface{}) bool {\n\tcustomMsg := \"\"\n\tif len(customMsgs) \u003e 0 {\n\t\tcustomMsg = strings.Join(customMsgs, \" \")\n\t}\n\tif customMsg != \"\" {\n\t\tfailureMessage += \" - \" + customMsg\n\t}\n\tt.Errorf(failureMessage, args...)\n\treturn false\n}\n\nfunc autofail(t TestingT, success bool, customMsgs []string, failureMessage string, args ...interface{}) bool {\n\tif success {\n\t\treturn true\n\t}\n\treturn fail(t, customMsgs, failureMessage, args...)\n}\n\nfunc checkDidPanic(f func()) (didPanic bool, message string) {\n\tdidPanic = true\n\tdefer func() {\n\t\tr := recover()\n\n\t\tif r == nil {\n\t\t\tmessage = \"nil\"\n\t\t\treturn\n\t\t}\n\n\t\terr, ok := r.(error)\n\t\tif ok {\n\t\t\tmessage = err.Error()\n\t\t\treturn\n\t\t}\n\n\t\terrStr, ok := r.(string)\n\t\tif ok {\n\t\t\tmessage = errStr\n\t\t\treturn\n\t\t}\n\n\t\tmessage = \"recover: unsupported type\"\n\t}()\n\tf()\n\tdidPanic = false\n\treturn\n}\n" + }, + { + "name": "mock_test.gno", + "body": "package uassert\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype mockTestingT struct {\n\tfmt string\n\targs []interface{}\n}\n\n// --- interface mock\n\nvar _ TestingT = (*mockTestingT)(nil)\n\nfunc (mockT *mockTestingT) Helper() { /* noop */ }\nfunc (mockT *mockTestingT) Skip(args ...interface{}) { /* not implmented */ }\nfunc (mockT *mockTestingT) Fail() { /* not implmented */ }\nfunc (mockT *mockTestingT) FailNow() { /* not implmented */ }\nfunc (mockT *mockTestingT) Logf(fmt string, args ...interface{}) { /* noop */ }\n\nfunc (mockT *mockTestingT) Fatalf(fmt string, args ...interface{}) {\n\tmockT.fmt = \"fatal: \" + fmt\n\tmockT.args = args\n}\n\nfunc (mockT *mockTestingT) Errorf(fmt string, args ...interface{}) {\n\tmockT.fmt = \"error: \" + fmt\n\tmockT.args = args\n}\n\n// --- helpers\n\nfunc (mockT *mockTestingT) actualString() string {\n\tres := fmt.Sprintf(mockT.fmt, mockT.args...)\n\tmockT.reset()\n\treturn res\n}\n\nfunc (mockT *mockTestingT) reset() {\n\tmockT.fmt = \"\"\n\tmockT.args = nil\n}\n\nfunc (mockT *mockTestingT) equals(t *testing.T, expected string) {\n\tactual := mockT.actualString()\n\n\tif expected != actual {\n\t\tt.Errorf(\"mockT differs:\\n- expected: %s\\n- actual: %s\\n\", expected, actual)\n\t}\n}\n\nfunc (mockT *mockTestingT) empty(t *testing.T) {\n\tif mockT.fmt != \"\" || mockT.args != nil {\n\t\tactual := mockT.actualString()\n\t\tt.Errorf(\"mockT should be empty, got %s\", actual)\n\t}\n}\n" + }, + { + "name": "types.gno", + "body": "package uassert\n\ntype TestingT interface {\n\tHelper()\n\tSkip(args ...interface{})\n\tFatalf(fmt string, args ...interface{})\n\tErrorf(fmt string, args ...interface{})\n\tLogf(fmt string, args ...interface{})\n\tFail()\n\tFailNow()\n}\n" + }, + { + "name": "uassert.gno", + "body": "// uassert is an adapted lighter version of https://github.com/stretchr/testify/assert.\npackage uassert\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// NoError asserts that a function returned no error (i.e. `nil`).\nfunc NoError(t TestingT, err error, msgs ...string) bool {\n\tt.Helper()\n\tif err != nil {\n\t\treturn fail(t, msgs, \"unexpected error: %s\", err.Error())\n\t}\n\treturn true\n}\n\n// Error asserts that a function returned an error (i.e. not `nil`).\nfunc Error(t TestingT, err error, msgs ...string) bool {\n\tt.Helper()\n\tif err == nil {\n\t\treturn fail(t, msgs, \"an error is expected but got nil\")\n\t}\n\treturn true\n}\n\n// ErrorContains asserts that a function returned an error (i.e. not `nil`)\n// and that the error contains the specified substring.\nfunc ErrorContains(t TestingT, err error, contains string, msgs ...string) bool {\n\tt.Helper()\n\n\tif !Error(t, err, msgs...) {\n\t\treturn false\n\t}\n\n\tactual := err.Error()\n\tif !strings.Contains(actual, contains) {\n\t\treturn fail(t, msgs, \"error %q does not contain %q\", actual, contains)\n\t}\n\n\treturn true\n}\n\n// True asserts that the specified value is true.\nfunc True(t TestingT, value bool, msgs ...string) bool {\n\tt.Helper()\n\tif !value {\n\t\treturn fail(t, msgs, \"should be true\")\n\t}\n\treturn true\n}\n\n// False asserts that the specified value is false.\nfunc False(t TestingT, value bool, msgs ...string) bool {\n\tt.Helper()\n\tif value {\n\t\treturn fail(t, msgs, \"should be false\")\n\t}\n\treturn true\n}\n\n// ErrorIs asserts the given error matches the target error\nfunc ErrorIs(t TestingT, err, target error, msgs ...string) bool {\n\tt.Helper()\n\n\tif err == nil || target == nil {\n\t\treturn err == target\n\t}\n\n\t// XXX: if errors.Is(err, target) return true\n\n\tif err.Error() != target.Error() {\n\t\treturn fail(t, msgs, \"error mismatch, expected %s, got %s\", target.Error(), err.Error())\n\t}\n\n\treturn true\n}\n\n// PanicsWithMessage asserts that the code inside the specified func panics,\n// and that the recovered panic value satisfies the given message\nfunc PanicsWithMessage(t TestingT, msg string, f func(), msgs ...string) bool {\n\tt.Helper()\n\n\tdidPanic, panicValue := checkDidPanic(f)\n\tif !didPanic {\n\t\treturn fail(t, msgs, \"func should panic\\n\\tPanic value:\\t%v\", panicValue)\n\t}\n\n\tif panicValue != msg {\n\t\treturn fail(t, msgs, \"func should panic with message:\\t%s\\n\\tPanic value:\\t%s\", msg, panicValue)\n\t}\n\treturn true\n}\n\n// NotPanics asserts that the code inside the specified func does NOT panic.\nfunc NotPanics(t TestingT, f func(), msgs ...string) bool {\n\tt.Helper()\n\n\tdidPanic, panicValue := checkDidPanic(f)\n\n\tif didPanic {\n\t\treturn fail(t, msgs, \"func should not panic\\n\\tPanic value:\\t%s\", panicValue)\n\t}\n\treturn true\n}\n\n// Equal asserts that two objects are equal.\nfunc Equal(t TestingT, expected, actual interface{}, msgs ...string) bool {\n\tt.Helper()\n\n\tif expected == nil || actual == nil {\n\t\treturn expected == actual\n\t}\n\n\t// XXX: errors\n\t// XXX: slices\n\t// XXX: pointers\n\n\tequal := false\n\tok_ := false\n\tes, as := \"unsupported type\", \"unsupported type\"\n\n\tswitch ev := expected.(type) {\n\tcase string:\n\t\tif av, ok := actual.(string); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = ev, av\n\t\t}\n\tcase std.Address:\n\t\tif av, ok := actual.(std.Address); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = string(ev), string(av)\n\t\t}\n\tcase int:\n\t\tif av, ok := actual.(int); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(ev), strconv.Itoa(av)\n\t\t}\n\tcase int8:\n\t\tif av, ok := actual.(int8); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int16:\n\t\tif av, ok := actual.(int16); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int32:\n\t\tif av, ok := actual.(int32); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int64:\n\t\tif av, ok := actual.(int64); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase uint:\n\t\tif av, ok := actual.(uint); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint8:\n\t\tif av, ok := actual.(uint8); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint16:\n\t\tif av, ok := actual.(uint16); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint32:\n\t\tif av, ok := actual.(uint32); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint64:\n\t\tif av, ok := actual.(uint64); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10)\n\t\t}\n\tcase bool:\n\t\tif av, ok := actual.(bool); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tif ev {\n\t\t\t\tes, as = \"true\", \"false\"\n\t\t\t} else {\n\t\t\t\tes, as = \"false\", \"true\"\n\t\t\t}\n\t\t}\n\tcase float32:\n\t\tif av, ok := actual.(float32); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t}\n\tcase float64:\n\t\tif av, ok := actual.(float64); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t}\n\tdefault:\n\t\treturn fail(t, msgs, \"uassert.Equal: unsupported type\")\n\t}\n\n\t/*\n\t\t// XXX: implement stringer and other well known similar interfaces\n\t\ttype stringer interface{ String() string }\n\t\tif ev, ok := expected.(stringer); ok {\n\t\t\tif av, ok := actual.(stringer); ok {\n\t\t\t\tequal = ev.String() == av.String()\n\t\t\t\tok_ = true\n\t\t\t}\n\t\t}\n\t*/\n\n\tif !ok_ {\n\t\treturn fail(t, msgs, \"uassert.Equal: different types\") // XXX: display the types\n\t}\n\tif !equal {\n\t\treturn fail(t, msgs, \"uassert.Equal: same type but different value\\n\\texpected: %s\\n\\tactual: %s\", es, as)\n\t}\n\n\treturn true\n}\n\nfunc Empty(t TestingT, obj interface{}, msgs ...string) bool {\n\tt.Helper()\n\tswitch val := obj.(type) {\n\tcase string:\n\t\tif val != \"\" {\n\t\t\treturn fail(t, msgs, \"uassert.Empty: not empty string: %s\", val)\n\t\t}\n\tcase int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:\n\t\tif val != 0 {\n\t\t\treturn fail(t, msgs, \"uassert.Empty: not empty number: %d\", val)\n\t\t}\n\tcase std.Address:\n\t\tvar zeroAddr std.Address\n\t\tif val != zeroAddr {\n\t\t\treturn fail(t, msgs, \"uassert.Empty: not empty std.Address: %s\", string(val))\n\t\t}\n\tdefault:\n\t\treturn fail(t, msgs, \"uassert.Empty: unsupported type\")\n\t}\n\treturn true\n}\n" + }, + { + "name": "uassert_test.gno", + "body": "package uassert\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"std\"\n\t\"testing\"\n)\n\nvar _ TestingT = (*testing.T)(nil)\n\nfunc TestMock(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tmockT.empty(t)\n\tNoError(mockT, errors.New(\"foo\"))\n\tmockT.equals(t, \"error: unexpected error: foo\")\n\tNoError(mockT, errors.New(\"foo\"), \"custom message\")\n\tmockT.equals(t, \"error: unexpected error: foo - custom message\")\n\tNoError(mockT, errors.New(\"foo\"), \"custom\", \"message\")\n\tmockT.equals(t, \"error: unexpected error: foo - custom message\")\n}\n\nfunc TestNoError(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tTrue(t, NoError(mockT, nil))\n\tmockT.empty(t)\n\tFalse(t, NoError(mockT, errors.New(\"foo bar\")))\n\tmockT.equals(t, \"error: unexpected error: foo bar\")\n}\n\nfunc TestError(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tTrue(t, Error(mockT, errors.New(\"foo bar\")))\n\tmockT.empty(t)\n\tFalse(t, Error(mockT, nil))\n\tmockT.equals(t, \"error: an error is expected but got nil\")\n}\n\nfunc TestErrorContains(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\t// nil error\n\tvar err error\n\tFalse(t, ErrorContains(mockT, err, \"\"), \"ErrorContains should return false for nil arg\")\n}\n\nfunc TestTrue(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tif !True(mockT, true) {\n\t\tt.Error(\"True should return true\")\n\t}\n\tmockT.empty(t)\n\tif True(mockT, false) {\n\t\tt.Error(\"True should return false\")\n\t}\n\tmockT.equals(t, \"error: should be true\")\n}\n\nfunc TestFalse(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tif !False(mockT, false) {\n\t\tt.Error(\"False should return true\")\n\t}\n\tmockT.empty(t)\n\tif False(mockT, true) {\n\t\tt.Error(\"False should return false\")\n\t}\n\tmockT.equals(t, \"error: should be false\")\n}\n\nfunc TestPanicsWithMessage(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tif !PanicsWithMessage(mockT, \"panic\", func() {\n\t\tpanic(errors.New(\"panic\"))\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return true\")\n\t}\n\tmockT.empty(t)\n\n\tif PanicsWithMessage(mockT, \"Panic!\", func() {\n\t\t// noop\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return false\")\n\t}\n\tmockT.equals(t, \"error: func should panic\\n\\tPanic value:\\tnil\")\n\n\tif PanicsWithMessage(mockT, \"at the disco\", func() {\n\t\tpanic(errors.New(\"panic\"))\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return false\")\n\t}\n\tmockT.equals(t, \"error: func should panic with message:\\tat the disco\\n\\tPanic value:\\tpanic\")\n\n\tif PanicsWithMessage(mockT, \"Panic!\", func() {\n\t\tpanic(\"panic\")\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return false\")\n\t}\n\tmockT.equals(t, \"error: func should panic with message:\\tPanic!\\n\\tPanic value:\\tpanic\")\n}\n\nfunc TestNotPanics(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tif !NotPanics(mockT, func() {\n\t\t// noop\n\t}) {\n\t\tt.Error(\"NotPanics should return true\")\n\t}\n\tmockT.empty(t)\n\n\tif NotPanics(mockT, func() {\n\t\tpanic(\"Panic!\")\n\t}) {\n\t\tt.Error(\"NotPanics should return false\")\n\t}\n}\n\nfunc TestEqual(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tcases := []struct {\n\t\texpected interface{}\n\t\tactual interface{}\n\t\tresult bool\n\t\tremark string\n\t}{\n\t\t// expected to be equal\n\t\t{\"Hello World\", \"Hello World\", true, \"\"},\n\t\t{123, 123, true, \"\"},\n\t\t{123.5, 123.5, true, \"\"},\n\t\t{nil, nil, true, \"\"},\n\t\t{int32(123), int32(123), true, \"\"},\n\t\t{uint64(123), uint64(123), true, \"\"},\n\t\t{std.Address(\"g12345\"), std.Address(\"g12345\"), true, \"\"},\n\t\t// XXX: continue\n\n\t\t// not expected to be equal\n\t\t{\"Hello World\", 42, false, \"\"},\n\t\t{41, 42, false, \"\"},\n\t\t{10, uint(10), false, \"\"},\n\t\t// XXX: continue\n\n\t\t// expected to raise errors\n\t\t// XXX: todo\n\t}\n\n\tfor _, c := range cases {\n\t\tname := fmt.Sprintf(\"Equal(%v, %v)\", c.expected, c.actual)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres := Equal(mockT, c.expected, c.actual)\n\n\t\t\tif res != c.result {\n\t\t\t\tt.Errorf(\"%s should return %v: %s - %s\", name, c.result, c.remark, mockT.actualString())\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype myStruct struct {\n\tS string\n\tI int\n}\n\nfunc TestEmpty(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tcases := []struct {\n\t\tobj interface{}\n\t\texpectedEmpty bool\n\t}{\n\t\t// expected to be empty\n\t\t{\"\", true},\n\t\t{0, true},\n\t\t{int(0), true},\n\t\t{int64(0), true},\n\t\t{uint(0), true},\n\t\t// XXX: continue\n\n\t\t// not expected to be empty\n\t\t{\"Hello World\", false},\n\t\t{1, false},\n\t\t{int32(1), false},\n\t\t{uint64(1), false},\n\t\t{std.Address(\"g12345\"), false},\n\n\t\t// unsupported\n\t\t{nil, false},\n\t\t{myStruct{}, false},\n\t\t{\u0026myStruct{}, false},\n\t}\n\n\tfor _, c := range cases {\n\t\tname := fmt.Sprintf(\"Empty(%v)\", c.obj)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres := Empty(mockT, c.obj)\n\n\t\t\tif res != c.expectedEmpty {\n\t\t\t\tt.Errorf(\"%s should return %v: %s\", name, c.expectedEmpty, mockT.actualString())\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "urequire", + "path": "gno.land/p/demo/urequire", + "files": [ + { + "name": "urequire.gno", + "body": "// urequire is a sister package for uassert.\n// XXX: codegen the package.\npackage urequire\n\nimport \"gno.land/p/demo/uassert\"\n\n// type TestingT = uassert.TestingT // XXX: bug, should work\n\nfunc NoError(t uassert.TestingT, err error, msgs ...string) {\n\tt.Helper()\n\tif uassert.NoError(t, err, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc Error(t uassert.TestingT, err error, msgs ...string) {\n\tt.Helper()\n\tif uassert.Error(t, err, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc ErrorContains(t uassert.TestingT, err error, contains string, msgs ...string) {\n\tt.Helper()\n\tif uassert.ErrorContains(t, err, contains, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc True(t uassert.TestingT, value bool, msgs ...string) {\n\tt.Helper()\n\tif uassert.True(t, value, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc False(t uassert.TestingT, value bool, msgs ...string) {\n\tt.Helper()\n\tif uassert.False(t, value, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc ErrorIs(t uassert.TestingT, err, target error, msgs ...string) {\n\tt.Helper()\n\tif uassert.ErrorIs(t, err, target, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc PanicsWithMessage(t uassert.TestingT, msg string, f func(), msgs ...string) {\n\tt.Helper()\n\tif uassert.PanicsWithMessage(t, msg, f, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc NotPanics(t uassert.TestingT, f func(), msgs ...string) {\n\tt.Helper()\n\tif uassert.NotPanics(t, f, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc Equal(t uassert.TestingT, expected, actual interface{}, msgs ...string) {\n\tt.Helper()\n\tif uassert.Equal(t, expected, actual, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n\nfunc Empty(t uassert.TestingT, obj interface{}, msgs ...string) {\n\tt.Helper()\n\tif uassert.Empty(t, obj, msgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n" + }, + { + "name": "urequire_test.gno", + "body": "package urequire\n\nimport \"testing\"\n\nfunc TestPackage(t *testing.T) {\n\tEqual(t, 42, 42)\n\t// XXX: find a way to unit test this package\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "grc20", + "path": "gno.land/p/demo/grc/grc20", + "files": [ + { + "name": "banker.gno", + "body": "package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\n\tif name == \"\" {\n\t\tpanic(\"name should not be empty\")\n\t}\n\tif symbol == \"\" {\n\t\tpanic(\"symbol should not be empty\")\n\t}\n\t// XXX additional checks (length, characters, limits, etc)\n\n\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tTransferEvent,\n\t\t\"from\", \"\",\n\t\t\"to\", string(address),\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tTransferEvent,\n\t\t\"from\", string(address),\n\t\t\"to\", \"\",\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) SpendAllowance(owner, spender std.Address, amount uint64) error {\n\tif !owner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif !spender.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcurrentAllowance := b.Allowance(owner, spender)\n\tif currentAllowance \u003c amount {\n\t\treturn ErrInsufficientAllowance\n\t}\n\n\tkey := allowanceKey(owner, spender)\n\tnewAllowance := currentAllowance - amount\n\n\tif newAllowance == 0 {\n\t\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) Transfer(from, to std.Address, amount uint64) error {\n\tif !from.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif !to.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\t// debug.\n\t// println(\"from\", from, \"to\", to, \"amount\", amount, \"fromBalance\", fromBalance, \"toBalance\", toBalance)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.balances.Set(string(from), newFromBalance)\n\n\tstd.Emit(\n\t\tTransferEvent,\n\t\t\"from\", from.String(),\n\t\t\"to\", to.String(),\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\treturn nil\n}\n\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(owner, spender std.Address, amount uint64) error {\n\tif !owner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif !spender.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tb.allowances.Set(allowanceKey(owner, spender), amount)\n\n\tstd.Emit(\n\t\tApprovalEvent,\n\t\t\"owner\", string(owner),\n\t\t\"spender\", string(spender),\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n" + }, + { + "name": "banker_test.gno", + "body": "package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n" + }, + { + "name": "token.gno", + "body": "package grc20\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be explosed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PrevRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n\ntype Token2 interface {\n\texts.TokenMetadata\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() uint64\n\n\t// Returns the amount of tokens owned by `account`.\n\tBalanceOf(account std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `to`.\n\t//\n\t// Returns an error if the operation failed.\n\tTransfer(to std.Address, amount uint64) error\n\n\t// Returns the remaining number of tokens that `spender` will be\n\t// allowed to spend on behalf of `owner` through {transferFrom}. This is\n\t// zero by default.\n\t//\n\t// This value changes when {approve} or {transferFrom} are called.\n\tAllowance(owner, spender std.Address) uint64\n\n\t// Sets `amount` as the allowance of `spender` over the caller's tokens.\n\t//\n\t// Returns an error if the operation failed.\n\t//\n\t// IMPORTANT: Beware that changing an allowance with this method brings the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// desired value afterwards:\n\t// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n\tApprove(spender std.Address, amount uint64) error\n\n\t// Moves `amount` tokens from `from` to `to` using the\n\t// allowance mechanism. `amount` is then deducted from the caller's\n\t// allowance.\n\t//\n\t// Returns an error if the operation failed.\n\tTransferFrom(from, to std.Address, amount uint64) error\n}\n" + }, + { + "name": "token_test.gno", + "body": "package grc20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOrigCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOrigCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n" + }, + { + "name": "types.gno", + "body": "package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\nvar (\n\tErrInsufficientBalance = errors.New(\"insufficient balance\")\n\tErrInsufficientAllowance = errors.New(\"insufficient allowance\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n)\n\ntype Token interface {\n\texts.TokenMetadata\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() uint64\n\n\t// Returns the amount of tokens owned by `account`.\n\tBalanceOf(account std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `to`.\n\t//\n\t// Returns an error if the operation failed.\n\tTransfer(to std.Address, amount uint64) error\n\n\t// Returns the remaining number of tokens that `spender` will be\n\t// allowed to spend on behalf of `owner` through {transferFrom}. This is\n\t// zero by default.\n\t//\n\t// This value changes when {approve} or {transferFrom} are called.\n\tAllowance(owner, spender std.Address) uint64\n\n\t// Sets `amount` as the allowance of `spender` over the caller's tokens.\n\t//\n\t// Returns an error if the operation failed.\n\t//\n\t// IMPORTANT: Beware that changing an allowance with this method brings the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// desired value afterwards:\n\t// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n\tApprove(spender std.Address, amount uint64) error\n\n\t// Moves `amount` tokens from `from` to `to` using the\n\t// allowance mechanism. `amount` is then deducted from the caller's\n\t// allowance.\n\t//\n\t// Returns an error if the operation failed.\n\tTransferFrom(from, to std.Address, amount uint64) error\n}\n\nconst (\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\n)\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "grc721", + "path": "gno.land/p/demo/grc/grc721", + "files": [ + { + "name": "basic_nft.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n" + }, + { + "name": "basic_nft_test.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\tname := dummy.Name()\n\tif name != dummyNFTName {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", dummyNFTName, name)\n\t}\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\tsymbol := dummy.Symbol()\n\tif symbol != dummyNFTSymbol {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", dummyNFTSymbol, symbol)\n\t}\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcount := dummy.TokenCount()\n\tif count != 0 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 0, count)\n\t}\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tif count != 2 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 2, count)\n\t}\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceAddr1 != 0 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 0, balanceAddr1)\n\t}\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tif balanceAddr1 != 2 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 2, balanceAddr1)\n\t}\n\tif balanceAddr2 != 1 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 1, balanceAddr2)\n\t}\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif owner != addr1 {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", addr1.String(), owner.String())\n\t}\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif owner != addr2 {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", addr2.String(), owner.String())\n\t}\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tif isApprovedForAll != false {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", false, isApprovedForAll)\n\t}\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tif isApprovedForAll != false {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", false, isApprovedForAll)\n\t}\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tif isApprovedForAll != true {\n\t\tt.Errorf(\"expected: (%v), got: (%v)\", false, isApprovedForAll)\n\t}\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif approvedAddr != addr {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", addr.String(), approvedAddr.String())\n\t}\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceOfCaller != 1 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 1, balanceOfCaller)\n\t}\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceOfAddr != 1 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 1, balanceOfAddr)\n\t}\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif owner != addr {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", addr.String(), owner.String())\n\t}\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceOfCaller != 1 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 1, balanceOfCaller)\n\t}\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif balanceOfAddr != 1 {\n\t\tt.Errorf(\"expected: (%d), got: (%d)\", 1, balanceOfAddr)\n\t}\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif owner != addr {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", addr.String(), owner.String())\n\t}\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\tif owner != addr1 {\n\t\tt.Errorf(\"expected: (%s), got: (%s)\", addr1.String(), owner.String())\n\t}\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"should not result in error\")\n\t}\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tif err == nil {\n\t\tt.Errorf(\"should result in error\")\n\t}\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\n\tif derr != nil {\n\t\tt.Errorf(\"Should not result in error \", derr.Error())\n\t}\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tif err != ErrInvalidTokenId {\n\t\tt.Errorf(\"Expected error %v, got %v\", ErrInvalidTokenId, err)\n\t}\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tif cerr != ErrCallerIsNotOwner {\n\t\tt.Errorf(\"Expected error %v, got %v\", ErrCallerIsNotOwner, err)\n\t}\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"TokenURI error: %v, \", err.Error())\n\t}\n\tif dummyTokenURI != tokenURI {\n\t\tt.Errorf(\"Expected URI %v, got %v\", tokenURI, dummyTokenURI)\n\t}\n}\n" + }, + { + "name": "errors.gno", + "body": "package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n" + }, + { + "name": "grc721_metadata.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n" + }, + { + "name": "grc721_metadata_test.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tif derr != nil {\n\t\tt.Errorf(\"Should not result in error : %s\", derr.Error())\n\t}\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tif err != ErrInvalidTokenId {\n\t\tt.Errorf(\"Expected error %s, got %s\", ErrInvalidTokenId, err)\n\t}\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tif cerr != ErrCallerIsNotOwner {\n\t\tt.Errorf(\"Expected error %s, got %s\", ErrCallerIsNotOwner, cerr)\n\t}\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tif err != nil {\n\t\tt.Errorf(\"Metadata error: %s\", err.Error())\n\t}\n\n\t// Check if metadata attributes match expected values\n\tif dummyMetadata.Image != image {\n\t\tt.Errorf(\"Expected Metadata's image %s, got %s\", image, dummyMetadata.Image)\n\t}\n\tif dummyMetadata.ImageData != imageData {\n\t\tt.Errorf(\"Expected Metadata's imageData %s, got %s\", imageData, dummyMetadata.ImageData)\n\t}\n\tif dummyMetadata.ExternalURL != externalURL {\n\t\tt.Errorf(\"Expected Metadata's externalURL %s, got %s\", externalURL, dummyMetadata.ExternalURL)\n\t}\n\tif dummyMetadata.Description != description {\n\t\tt.Errorf(\"Expected Metadata's description %s, got %s\", description, dummyMetadata.Description)\n\t}\n\tif dummyMetadata.Name != name {\n\t\tt.Errorf(\"Expected Metadata's name %s, got %s\", name, dummyMetadata.Name)\n\t}\n\tif len(dummyMetadata.Attributes) != len(attributes) {\n\t\tt.Errorf(\"Expected %d Metadata's attributes, got %d\", len(attributes), len(dummyMetadata.Attributes))\n\t}\n\tif dummyMetadata.BackgroundColor != backgroundColor {\n\t\tt.Errorf(\"Expected Metadata's backgroundColor %s, got %s\", backgroundColor, dummyMetadata.BackgroundColor)\n\t}\n\tif dummyMetadata.AnimationURL != animationURL {\n\t\tt.Errorf(\"Expected Metadata's animationURL %s, got %s\", animationURL, dummyMetadata.AnimationURL)\n\t}\n\tif dummyMetadata.YoutubeURL != youtubeURL {\n\t\tt.Errorf(\"Expected Metadata's youtubeURL %s, got %s\", youtubeURL, dummyMetadata.YoutubeURL)\n\t}\n}\n" + }, + { + "name": "grc721_royalty.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n" + }, + { + "name": "grc721_royalty_test.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\n\tif derr != nil {\n\t\tt.Errorf(\"Should not result in error : %s\", derr.Error())\n\t}\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tif err != ErrInvalidTokenId {\n\t\tt.Errorf(\"Expected error %s, got %s\", ErrInvalidTokenId, err)\n\t}\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tif cerr != ErrCallerIsNotOwner {\n\t\tt.Errorf(\"Expected error %s, got %s\", ErrCallerIsNotOwner, cerr)\n\t}\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tif aerr != ErrInvalidRoyaltyPaymentAddress {\n\t\tt.Errorf(\"Expected error %s, got %s\", ErrInvalidRoyaltyPaymentAddress, aerr)\n\t}\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\n\tif perr != ErrInvalidRoyaltyPercentage {\n\t\tt.Errorf(\"Expected error %s, got %s\", ErrInvalidRoyaltyPercentage, perr)\n\t}\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tif rerr != nil {\n\t\tt.Errorf(\"RoyaltyInfo error: %s\", rerr.Error())\n\t}\n\n\tif dummyPaymentAddress != paymentAddress {\n\t\tt.Errorf(\"Expected RoyaltyPaymentAddress %s, got %s\", paymentAddress, dummyPaymentAddress)\n\t}\n\n\tif dummyRoyaltyAmount != expectRoyaltyAmount {\n\t\tt.Errorf(\"Expected RoyaltyAmount %d, got %d\", expectRoyaltyAmount, dummyRoyaltyAmount)\n\t}\n}\n" + }, + { + "name": "igrc721.gno", + "body": "package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n" + }, + { + "name": "igrc721_metadata.gno", + "body": "package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n" + }, + { + "name": "igrc721_royalty.gno", + "body": "package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n" + }, + { + "name": "util.gno", + "body": "package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "grc777", + "path": "gno.land/p/demo/grc/grc777", + "files": [ + { + "name": "dummy_test.gno", + "body": "package grc777\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\ntype dummyImpl struct{}\n\n// FIXME: this should fail.\nvar _ IGRC777 = (*dummyImpl)(nil)\n\nfunc TestInterface(t *testing.T) {\n\tvar dummy IGRC777 = \u0026dummyImpl{}\n}\n\nfunc (impl *dummyImpl) GetName() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetSymbol() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetDecimals() uint { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Granularity() (granularity uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) TotalSupply() (supply uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) BalanceOf(address std.Address) uint64 { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Burn(amount uint64, data []byte) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) AuthorizeOperator(operator std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) RevokeOperator(operators std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) DefaultOperators() []std.Address { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Send(recipient std.Address, amount uint64, data []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) IsOperatorFor(operator, tokenHolder std.Address) bool {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorBurn(account std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n" + }, + { + "name": "igrc777.gno", + "body": "package grc777\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// TODO: use big.Int or a custom uint64 instead of uint64\n\ntype IGRC777 interface {\n\texts.TokenMetadata\n\n\t// Returns the smallest part of the token that is not divisible. This\n\t// means all token operations (creation, movement and destruction) must\n\t// have amounts that are a multiple of this number.\n\t//\n\t// For most token contracts, this value will equal 1.\n\tGranularity() (granularity uint64)\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() (supply uint64)\n\n\t// Returns the amount of tokens owned by an account (`owner`).\n\tBalanceOf(address std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `recipient`.\n\t//\n\t// If send or receive hooks are registered for the caller and `recipient`,\n\t// the corresponding functions will be called with `data` and empty\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tSend(recipient std.Address, amount uint64, data []byte)\n\n\t// Destroys `amount` tokens from the caller's account, reducing the\n\t// total supply.\n\t//\n\t// If a send hook is registered for the caller, the corresponding function\n\t// will be called with `data` and empty `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\tBurn(amount uint64, data []byte)\n\n\t// Returns true if an account is an operator of `tokenHolder`.\n\t// Operators can send and burn tokens on behalf of their owners. All\n\t// accounts are their own operator.\n\t//\n\t// See {operatorSend} and {operatorBurn}.\n\tIsOperatorFor(operator, tokenHolder std.Address) bool\n\n\t// Make an account an operator of the caller.\n\t//\n\t// See {isOperatorFor}.\n\t//\n\t// Emits an {AuthorizedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tAuthorizeOperator(operator std.Address)\n\n\t// Revoke an account's operator status for the caller.\n\t//\n\t// See {isOperatorFor} and {defaultOperators}.\n\t//\n\t// Emits a {RevokedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tRevokeOperator(operators std.Address)\n\n\t// Returns the list of default operators. These accounts are operators\n\t// for all token holders, even if {authorizeOperator} was never called on\n\t// them.\n\t//\n\t// This list is immutable, but individual holders may revoke these via\n\t// {revokeOperator}, in which case {isOperatorFor} will return false.\n\tDefaultOperators() []std.Address\n\n\t// Moves `amount` tokens from `sender` to `recipient`. The caller must\n\t// be an operator of `sender`.\n\t//\n\t// If send or receive hooks are registered for `sender` and `recipient`,\n\t// the corresponding functions will be called with `data` and\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - `sender` cannot be the zero address.\n\t// - `sender` must have at least `amount` tokens.\n\t// - the caller must be an operator for `sender`.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tOperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte)\n\n\t// Destroys `amount` tokens from `account`, reducing the total supply.\n\t// The caller must be an operator of `account`.\n\t//\n\t// If a send hook is registered for `account`, the corresponding function\n\t// will be called with `data` and `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - `account` cannot be the zero address.\n\t// - `account` must have at least `amount` tokens.\n\t// - the caller must be an operator for `account`.\n\tOperatorBurn(account std.Address, amount uint64, data, operatorData []byte)\n}\n\n// Emitted when `amount` tokens are created by `operator` and assigned to `to`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype MintedEvent struct {\n\tOperator std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` destroys `amount` tokens from `account`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype BurnedEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` is made operator for `tokenHolder`\ntype AuthorizedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\n// Emitted when `operator` is revoked its operator status for `tokenHolder`.\ntype RevokedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\ntype SentEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "rat", + "path": "gno.land/p/demo/rat", + "files": [ + { + "name": "maths.gno", + "body": "package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n" + }, + { + "name": "rat.gno", + "body": "package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\n//}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "users", + "path": "gno.land/p/demo/users", + "files": [ + { + "name": "types.gno", + "body": "package users\n\ntype AddressOrName string\n\nfunc (aon AddressOrName) IsName() bool {\n\treturn aon != \"\" \u0026\u0026 aon[0] == '@'\n}\n\nfunc (aon AddressOrName) GetName() (string, bool) {\n\tif len(aon) \u003e= 2 \u0026\u0026 aon[0] == '@' {\n\t\treturn string(aon[1:]), true\n\t}\n\treturn \"\", false\n}\n" + }, + { + "name": "users.gno", + "body": "package users\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Types\n\ntype User struct {\n\tAddress std.Address\n\tName string\n\tProfile string\n\tNumber int\n\tInvites int\n\tInviter std.Address\n}\n\nfunc (u *User) Render() string {\n\tstr := \"## user \" + u.Name + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\" * address = \" + string(u.Address) + \"\\n\" +\n\t\t\" * \" + strconv.Itoa(u.Invites) + \" invites\\n\"\n\tif u.Inviter != \"\" {\n\t\tstr = str + \" * invited by \" + string(u.Inviter) + \"\\n\"\n\t}\n\tstr = str + \"\\n\" +\n\t\tu.Profile + \"\\n\"\n\treturn str\n}\n" + }, + { + "name": "users_test.gno", + "body": "package users\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "users", + "path": "gno.land/r/demo/users", + "files": [ + { + "name": "preregister.gno", + "body": "package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n" + }, + { + "name": "users.gno", + "body": "package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.GetOrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.GetOrigSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.GetOrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.GetOrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.GetOrigCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHome()\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome() string {\n\tdoc := \"\"\n\tname2User.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tuser := value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t\treturn false\n\t})\n\treturn doc\n}\n" + }, + { + "name": "z_0_b_filetest.gno", + "body": "package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n" + }, + { + "name": "z_0_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOrigSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n" + }, + { + "name": "z_10_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.GetOrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_11_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n" + }, + { + "name": "z_11b_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOrigCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_1_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_2_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_3_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_4_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n" + }, + { + "name": "z_5_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [x](/r/demo/users:x)\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n" + }, + { + "name": "z_6_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n" + }, + { + "name": "z_7_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_7b_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + }, + { + "name": "z_8_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOrigCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n" + }, + { + "name": "z_9_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "boards", + "path": "gno.land/r/demo/boards", + "files": [ + { + "name": "README.md", + "body": "This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote test3.gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid test3` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/gno.land/cmd/gnofaucet/README.md\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/users?help\u0026__func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards?help\u0026__func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards?help\u0026__func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards?help\u0026__func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n" + }, + { + "name": "board.gno", + "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=CreateThread\" +\n\t\t\"\u0026bid=\" + board.id.String() +\n\t\t\"\u0026body.type=textarea\"\n}\n" + }, + { + "name": "boards.gno", + "body": "package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n" + }, + { + "name": "misc.gno", + "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n" + }, + { + "name": "post.gno", + "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=CreateReply\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026threadid=\" + post.threadID.String() +\n\t\t\"\u0026postid=\" + post.id.String() +\n\t\t\"\u0026body.type=textarea\"\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=CreateRepost\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026postid=\" + post.id.String() +\n\t\t\"\u0026title.type=textarea\" +\n\t\t\"\u0026body.type=textarea\" +\n\t\t\"\u0026dstBoardID.type=textarea\"\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=DeletePost\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026threadid=\" + post.threadID.String() +\n\t\t\"\u0026postid=\" + post.id.String()\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n" + }, + { + "name": "public.gno", + "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.GetOrigSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n" + }, + { + "name": "render.gno", + "body": "package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n" + }, + { + "name": "role.gno", + "body": "package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n" + }, + { + "name": "z_0_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n" + }, + { + "name": "z_0_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n" + }, + { + "name": "z_0_c_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n" + }, + { + "name": "z_0_d_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n" + }, + { + "name": "z_0_e_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n" + }, + { + "name": "z_0_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards?help\u0026__func=CreateThread\u0026bid=1\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n" + }, + { + "name": "z_10_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n" + }, + { + "name": "z_10_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n" + }, + { + "name": "z_10_c_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n" + }, + { + "name": "z_10_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n" + }, + { + "name": "z_11_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n" + }, + { + "name": "z_11_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n" + }, + { + "name": "z_11_c_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n" + }, + { + "name": "z_11_d_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n" + }, + { + "name": "z_11_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n" + }, + { + "name": "z_12_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n" + }, + { + "name": "z_12_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n" + }, + { + "name": "z_12_c_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n" + }, + { + "name": "z_12_d_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n" + }, + { + "name": "z_12_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards?help\u0026__func=CreateThread\u0026bid=2\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n" + }, + { + "name": "z_1_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n" + }, + { + "name": "z_2_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n" + }, + { + "name": "z_3_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n" + }, + { + "name": "z_4_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n" + }, + { + "name": "z_5_b_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n" + }, + { + "name": "z_5_c_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n" + }, + { + "name": "z_5_d_filetest.gno", + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n" + }, + { + "name": "z_5_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n" + }, + { + "name": "z_6_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n" + }, + { + "name": "z_7_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards?help\u0026__func=CreateThread\u0026bid=1\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n" + }, + { + "name": "z_8_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n" + }, + { + "name": "z_9_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n" + }, + { + "name": "z_9_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n" + }, + { + "name": "z_9_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "groups", + "path": "gno.land/p/demo/groups", + "files": [ + { + "name": "groups.gno", + "body": "package groups\n\nimport \"gno.land/r/demo/boards\"\n\n// TODO implement something and test.\ntype Group struct {\n\tBoard *boards.Board\n}\n" + }, + { + "name": "vote_set.gno", + "body": "package groups\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/rat\"\n)\n\n//----------------------------------------\n// VoteSet\n\ntype VoteSet interface {\n\t// number of present votes in set.\n\tSize() int\n\t// add or update vote for voter.\n\tSetVote(voter std.Address, value string) error\n\t// count the number of votes for value.\n\tCountVotes(value string) int\n}\n\n//----------------------------------------\n// VoteList\n\ntype Vote struct {\n\tVoter std.Address\n\tValue string\n}\n\ntype VoteList []Vote\n\nfunc NewVoteList() *VoteList {\n\treturn \u0026VoteList{}\n}\n\nfunc (vlist *VoteList) Size() int {\n\treturn len(*vlist)\n}\n\nfunc (vlist *VoteList) SetVote(voter std.Address, value string) error {\n\t// TODO optimize with binary algorithm\n\tfor i, vote := range *vlist {\n\t\tif vote.Voter == voter {\n\t\t\t// update vote\n\t\t\t(*vlist)[i] = Vote{\n\t\t\t\tVoter: voter,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\t*vlist = append(*vlist, Vote{\n\t\tVoter: voter,\n\t\tValue: value,\n\t})\n\treturn nil\n}\n\nfunc (vlist *VoteList) CountVotes(target string) int {\n\t// TODO optimize with binary algorithm\n\tvar count int\n\tfor _, vote := range *vlist {\n\t\tif vote.Value == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\n//----------------------------------------\n// Committee\n\ntype Committee struct {\n\tQuorum rat.Rat\n\tThreshold rat.Rat\n\tAddresses std.AddressSet\n}\n\n//----------------------------------------\n// VoteSession\n// NOTE: this seems a bit too formal and\n// complicated vs what might be possible;\n// something simpler, more informal.\n\ntype SessionStatus int\n\nconst (\n\tSessionNew SessionStatus = iota\n\tSessionStarted\n\tSessionCompleted\n\tSessionCanceled\n)\n\ntype VoteSession struct {\n\tName string\n\tCreator std.Address\n\tBody string\n\tStart time.Time\n\tDeadline time.Time\n\tStatus SessionStatus\n\tCommittee *Committee\n\tVotes VoteSet\n\tChoices []string\n\tResult string\n}\n" + }, + { + "name": "z_1_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/p/demo/groups\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nvar vset groups.VoteSet\n\nfunc init() {\n\taddr1 := testutils.TestAddress(\"test1\")\n\taddr2 := testutils.TestAddress(\"test2\")\n\tvset = groups.NewVoteList()\n\tvset.SetVote(addr1, \"yes\")\n\tvset.SetVote(addr2, \"yes\")\n}\n\nfunc main() {\n\tprintln(vset.Size())\n\tprintln(\"yes:\", vset.CountVotes(\"yes\"))\n\tprintln(\"no:\", vset.CountVotes(\"no\"))\n}\n\n// Output:\n// 2\n// yes: 2\n// no: 0\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "uint256", + "path": "gno.land/p/demo/uint256", + "files": [ + { + "name": "LICENSE", + "body": "BSD 3-Clause License\n\nCopyright 2020 uint256 Authors\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, + { + "name": "README.md", + "body": "# Fixed size 256-bit math library\n\nThis is a library specialized at replacing the `big.Int` library for math based on 256-bit types.\n\noriginal repository: [uint256](\u003chttps://github.com/holiman/uint256/tree/master\u003e)\n" + }, + { + "name": "arithmetic.gno", + "body": "// arithmetic provides arithmetic operations for Uint objects.\n// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations\n// as well as overflow checks, and negation. These functions are essential for numeric\n// calculations using 256-bit unsigned integers.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Add sets z to the sum x+y\nfunc (z *Uint) Add(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred\nfunc (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Sub sets z to the difference x-y\nfunc (z *Uint) Sub(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed\nfunc (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Neg returns -x mod 2^256.\nfunc (z *Uint) Neg(x *Uint) *Uint {\n\treturn z.Sub(new(Uint), x)\n}\n\n// commented out for possible overflow\n// Mul sets z to the product x*y\nfunc (z *Uint) Mul(x, y *Uint) *Uint {\n\tvar (\n\t\tres Uint\n\t\tcarry uint64\n\t\tres1, res2, res3 uint64\n\t)\n\n\tcarry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tres3 = x.arr[3]*y.arr[0] + carry\n\n\tcarry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tres3 = res3 + x.arr[2]*y.arr[1] + carry\n\n\tcarry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tres3 = res3 + x.arr[1]*y.arr[2] + carry\n\n\tres.arr[3] = res3 + x.arr[0]*y.arr[3]\n\n\treturn z.Set(\u0026res)\n}\n\n// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred\nfunc (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) {\n\tp := umul(x, y)\n\tcopy(z.arr[:], p[:4])\n\treturn z, (p[4] | p[5] | p[6] | p[7]) != 0\n}\n\n// commented out for possible overflow\n// Div sets z to the quotient x/y for returns z.\n// If y == 0, z is set to 0\nfunc (z *Uint) Div(x, y *Uint) *Uint {\n\tif y.IsZero() || y.Gt(x) {\n\t\treturn z.Clear()\n\t}\n\tif x.Eq(y) {\n\t\treturn z.SetOne()\n\t}\n\t// Shortcut some cases\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() / y.Uint64())\n\t}\n\n\t// At this point, we know\n\t// x/y ; x \u003e y \u003e 0\n\n\tvar quot Uint\n\tudivrem(quot.arr[:], x.arr[:], y)\n\treturn z.Set(\u0026quot)\n}\n\n// MulMod calculates the modulo-m multiplication of x and y and\n// returns z.\n// If m == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) MulMod(x, y, m *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() || m.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tp := umul(x, y)\n\n\tif m.arr[3] != 0 {\n\t\tmu := Reciprocal(m)\n\t\tr := reduce4(p, m, mu)\n\t\treturn z.Set(\u0026r)\n\t}\n\n\tvar (\n\t\tpl Uint\n\t\tph Uint\n\t)\n\n\tpl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}}\n\tph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}}\n\n\t// If the multiplication is within 256 bits use Mod().\n\tif ph.IsZero() {\n\t\treturn z.Mod(\u0026pl, m)\n\t}\n\n\tvar quot [8]uint64\n\trem := udivrem(quot[:], p[:], m)\n\treturn z.Set(\u0026rem)\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Uint)\nfunc (z *Uint) Mod(x, y *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tswitch x.Cmp(y) {\n\tcase -1:\n\t\t// x \u003c y\n\t\tcopy(z.arr[:], x.arr[:])\n\t\treturn z\n\tcase 0:\n\t\t// x == y\n\t\treturn z.Clear() // They are equal\n\t}\n\n\t// At this point:\n\t// x != 0\n\t// y != 0\n\t// x \u003e y\n\n\t// Shortcut trivial case\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() % y.Uint64())\n\t}\n\n\tvar quot Uint\n\t*z = udivrem(quot.arr[:], x.arr[:], y)\n\treturn z\n}\n\n// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0.\n// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) {\n\tif y.IsZero() {\n\t\treturn z.Clear(), m.Clear()\n\t}\n\tvar quot Uint\n\t*m = udivrem(quot.arr[:], x.arr[:], y)\n\t*z = quot\n\treturn z, m\n}\n\n// Exp sets z = base**exponent mod 2**256, and returns z.\nfunc (z *Uint) Exp(base, exponent *Uint) *Uint {\n\tres := Uint{arr: [4]uint64{1, 0, 0, 0}}\n\tmultiplier := *base\n\texpBitLen := exponent.BitLen()\n\n\tcurBit := 0\n\tword := exponent.arr[0]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 64; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[1]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 128; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[2]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 192; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[3]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 256; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\treturn z.Set(\u0026res)\n}\n\nfunc (z *Uint) squared() {\n\tvar (\n\t\tres Uint\n\t\tcarry0, carry1, carry2 uint64\n\t\tres1, res2 uint64\n\t)\n\n\tcarry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0])\n\tcarry0, res1 = umulHop(carry0, z.arr[0], z.arr[1])\n\tcarry0, res2 = umulHop(carry0, z.arr[0], z.arr[2])\n\n\tcarry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1])\n\tcarry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1)\n\n\tcarry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2])\n\n\tres.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2\n\n\tz.Set(\u0026res)\n}\n\n// udivrem divides u by d and produces both quotient and remainder.\n// The quotient is stored in provided quot - len(u)-len(d)+1 words.\n// It loosely follows the Knuth's division algorithm (sometimes referenced as \"schoolbook\" division) using 64-bit words.\n// See Knuth, Volume 2, section 4.3.1, Algorithm D.\nfunc udivrem(quot, u []uint64, d *Uint) (rem Uint) {\n\tvar dLen int\n\tfor i := len(d.arr) - 1; i \u003e= 0; i-- {\n\t\tif d.arr[i] != 0 {\n\t\t\tdLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tshift := uint(bits.LeadingZeros64(d.arr[dLen-1]))\n\n\tvar dnStorage Uint\n\tdn := dnStorage.arr[:dLen]\n\tfor i := dLen - 1; i \u003e 0; i-- {\n\t\tdn[i] = (d.arr[i] \u003c\u003c shift) | (d.arr[i-1] \u003e\u003e (64 - shift))\n\t}\n\tdn[0] = d.arr[0] \u003c\u003c shift\n\n\tvar uLen int\n\tfor i := len(u) - 1; i \u003e= 0; i-- {\n\t\tif u[i] != 0 {\n\t\t\tuLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif uLen \u003c dLen {\n\t\tcopy(rem.arr[:], u)\n\t\treturn rem\n\t}\n\n\tvar unStorage [9]uint64\n\tun := unStorage[:uLen+1]\n\tun[uLen] = u[uLen-1] \u003e\u003e (64 - shift)\n\tfor i := uLen - 1; i \u003e 0; i-- {\n\t\tun[i] = (u[i] \u003c\u003c shift) | (u[i-1] \u003e\u003e (64 - shift))\n\t}\n\tun[0] = u[0] \u003c\u003c shift\n\n\t// TODO: Skip the highest word of numerator if not significant.\n\n\tif dLen == 1 {\n\t\tr := udivremBy1(quot, un, dn[0])\n\t\trem.SetUint64(r \u003e\u003e shift)\n\t\treturn rem\n\t}\n\n\tudivremKnuth(quot, un, dn)\n\n\tfor i := 0; i \u003c dLen-1; i++ {\n\t\trem.arr[i] = (un[i] \u003e\u003e shift) | (un[i+1] \u003c\u003c (64 - shift))\n\t}\n\trem.arr[dLen-1] = un[dLen-1] \u003e\u003e shift\n\n\treturn rem\n}\n\n// umul computes full 256 x 256 -\u003e 512 multiplication.\nfunc umul(x, y *Uint) [8]uint64 {\n\tvar (\n\t\tres [8]uint64\n\t\tcarry, carry4, carry5, carry6 uint64\n\t\tres1, res2, res3, res4, res5 uint64\n\t)\n\n\tcarry, res[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tcarry4, res3 = umulHop(carry, x.arr[3], y.arr[0])\n\n\tcarry, res[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tcarry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry)\n\tcarry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry)\n\n\tcarry, res[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tcarry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry)\n\tcarry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry)\n\tcarry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry)\n\n\tcarry, res[3] = umulHop(res3, x.arr[0], y.arr[3])\n\tcarry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry)\n\tcarry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry)\n\tres[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry)\n\n\treturn res\n}\n\n// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry.\nfunc umulStep(z, x, y, carry uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry = bits.Add64(lo, carry, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\tlo, carry = bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// umulHop computes (hi * 2^64 + lo) = z + (x * y)\nfunc umulHop(z, x, y uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry := bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// udivremBy1 divides u by single normalized word d and produces both quotient and remainder.\n// The quotient is stored in provided quot.\nfunc udivremBy1(quot, u []uint64, d uint64) (rem uint64) {\n\treciprocal := reciprocal2by1(d)\n\trem = u[len(u)-1] // Set the top word as remainder.\n\tfor j := len(u) - 2; j \u003e= 0; j-- {\n\t\tquot[j], rem = udivrem2by1(rem, u[j], d, reciprocal)\n\t}\n\treturn rem\n}\n\n// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm.\n// The quotient is stored in provided quot - len(u)-len(d) words.\n// Updates u to contain the remainder - len(d) words.\nfunc udivremKnuth(quot, u, d []uint64) {\n\tdh := d[len(d)-1]\n\tdl := d[len(d)-2]\n\treciprocal := reciprocal2by1(dh)\n\n\tfor j := len(u) - len(d) - 1; j \u003e= 0; j-- {\n\t\tu2 := u[j+len(d)]\n\t\tu1 := u[j+len(d)-1]\n\t\tu0 := u[j+len(d)-2]\n\n\t\tvar qhat, rhat uint64\n\t\tif u2 \u003e= dh { // Division overflows.\n\t\t\tqhat = ^uint64(0)\n\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t} else {\n\t\t\tqhat, rhat = udivrem2by1(u2, u1, dh, reciprocal)\n\t\t\tph, pl := bits.Mul64(qhat, dl)\n\t\t\tif ph \u003e rhat || (ph == rhat \u0026\u0026 pl \u003e u0) {\n\t\t\t\tqhat--\n\t\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t\t}\n\t\t}\n\n\t\t// Multiply and subtract.\n\t\tborrow := subMulTo(u[j:], d, qhat)\n\t\tu[j+len(d)] = u2 - borrow\n\t\tif u2 \u003c borrow { // Too much subtracted, add back.\n\t\t\tqhat--\n\t\t\tu[j+len(d)] += addTo(u[j:], d)\n\t\t}\n\n\t\tquot[j] = qhat // Store quotient digit.\n\t}\n}\n\n// isBitSet returns true if bit n-th is set, where n = 0 is LSB.\n// The n must be \u003c= 255.\nfunc (z *Uint) isBitSet(n uint) bool {\n\treturn (z.arr[n/64] \u0026 (1 \u003c\u003c (n % 64))) != 0\n}\n\n// addTo computes x += y.\n// Requires len(x) \u003e= len(y).\nfunc addTo(x, y []uint64) uint64 {\n\tvar carry uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\tx[i], carry = bits.Add64(x[i], y[i], carry)\n\t}\n\treturn carry\n}\n\n// subMulTo computes x -= y * multiplier.\n// Requires len(x) \u003e= len(y).\nfunc subMulTo(x, y []uint64, multiplier uint64) uint64 {\n\tvar borrow uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\ts, carry1 := bits.Sub64(x[i], borrow, 0)\n\t\tph, pl := bits.Mul64(y[i], multiplier)\n\t\tt, carry2 := bits.Sub64(s, pl, 0)\n\t\tx[i] = t\n\t\tborrow = ph + carry1 + carry2\n\t}\n\treturn borrow\n}\n\n// reciprocal2by1 computes \u003c^d, ^0\u003e / d.\nfunc reciprocal2by1(d uint64) uint64 {\n\treciprocal, _ := bits.Div64(^d, ^uint64(0), d)\n\treturn reciprocal\n}\n\n// udivrem2by1 divides \u003cuh, ul\u003e / d and produces both quotient and remainder.\n// It uses the provided d's reciprocal.\n// Implementation ported from https://github.com/chfast/intx and is based on\n// \"Improved division by invariant integers\", Algorithm 4.\nfunc udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) {\n\tqh, ql := bits.Mul64(reciprocal, uh)\n\tql, carry := bits.Add64(ql, ul, 0)\n\tqh, _ = bits.Add64(qh, uh, carry)\n\tqh++\n\n\tr := ul - qh*d\n\n\tif r \u003e ql {\n\t\tqh--\n\t\tr += d\n\t}\n\n\tif r \u003e= d {\n\t\tqh++\n\t\tr -= d\n\t}\n\n\treturn qh, r\n}\n" + }, + { + "name": "arithmetic_test.gno", + "body": "package uint256\n\nimport \"testing\"\n\ntype binOp2Test struct {\n\tx, y, want string\n}\n\nfunc TestAdd(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"3\", \"4\"},\n\t\t{\"10\", \"10\", \"20\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"1337\", \"30000\"},\n\t\t{\"2\", \"3\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"}, // underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Sub(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"10\", \"10\", \"100\"},\n\t\t{\"18446744073709551615\", \"2\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"10445\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"2\", \"31337\", \"2\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDivMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantDiv string\n\t\twantMod string\n\t}{\n\t\t{\"1\", \"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"1\", \"0\"},\n\t\t{\"100\", \"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"3\", \"10445\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\", \"0\"},\n\t\t{\"2\", \"31337\", \"0\", \"2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twantDiv, err := FromDecimal(tc.wantDiv)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twantMod, err := FromDecimal(tc.wantMod)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgotDiv := new(Uint)\n\t\tgotMod := new(Uint)\n\t\tgotDiv.DivMod(x, y, gotMod)\n\n\t\tfor i := range gotDiv.arr {\n\t\t\tif gotDiv.arr[i] != wantDiv.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Div %v, want Div %v\", tc.x, tc.y, gotDiv, wantDiv)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor i := range gotMod.arr {\n\t\t\tif gotMod.arr[i] != wantMod.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Mod %v, want Mod %v\", tc.x, tc.y, gotMod, wantMod)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"31337\", \"115792089237316195423570985008687907853269984665640564039457584007913129608599\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129608599\", \"31337\"},\n\t\t{\"0\", \"0\"},\n\t\t{\"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\"},\n\t\t{\"1\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Neg(x)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Neg(%s) = %v, want %v\", tc.x, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestExp(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"30773171189753\"},\n\t\t{\"31337\", \"0\", \"1\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"2\", \"3\", \"8\"},\n\t\t{\"2\", \"64\", \"18446744073709551616\"},\n\t\t{\"2\", \"128\", \"340282366920938463463374607431768211456\"},\n\t\t{\"2\", \"255\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"2\", \"256\", \"0\"}, // overflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Exp(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Exp(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n" + }, + { + "name": "bits_table.gno", + "body": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Code generated by go run make_tables.go. DO NOT EDIT.\n\npackage uint256\n\nconst ntz8tab = \"\" +\n\t\"\\x08\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x07\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\"\n\nconst pop8tab = \"\" +\n\t\"\\x00\\x01\\x01\\x02\\x01\\x02\\x02\\x03\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\\x05\\x06\\x06\\x07\\x06\\x07\\x07\\x08\"\n\nconst rev8tab = \"\" +\n\t\"\\x00\\x80\\x40\\xc0\\x20\\xa0\\x60\\xe0\\x10\\x90\\x50\\xd0\\x30\\xb0\\x70\\xf0\" +\n\t\"\\x08\\x88\\x48\\xc8\\x28\\xa8\\x68\\xe8\\x18\\x98\\x58\\xd8\\x38\\xb8\\x78\\xf8\" +\n\t\"\\x04\\x84\\x44\\xc4\\x24\\xa4\\x64\\xe4\\x14\\x94\\x54\\xd4\\x34\\xb4\\x74\\xf4\" +\n\t\"\\x0c\\x8c\\x4c\\xcc\\x2c\\xac\\x6c\\xec\\x1c\\x9c\\x5c\\xdc\\x3c\\xbc\\x7c\\xfc\" +\n\t\"\\x02\\x82\\x42\\xc2\\x22\\xa2\\x62\\xe2\\x12\\x92\\x52\\xd2\\x32\\xb2\\x72\\xf2\" +\n\t\"\\x0a\\x8a\\x4a\\xca\\x2a\\xaa\\x6a\\xea\\x1a\\x9a\\x5a\\xda\\x3a\\xba\\x7a\\xfa\" +\n\t\"\\x06\\x86\\x46\\xc6\\x26\\xa6\\x66\\xe6\\x16\\x96\\x56\\xd6\\x36\\xb6\\x76\\xf6\" +\n\t\"\\x0e\\x8e\\x4e\\xce\\x2e\\xae\\x6e\\xee\\x1e\\x9e\\x5e\\xde\\x3e\\xbe\\x7e\\xfe\" +\n\t\"\\x01\\x81\\x41\\xc1\\x21\\xa1\\x61\\xe1\\x11\\x91\\x51\\xd1\\x31\\xb1\\x71\\xf1\" +\n\t\"\\x09\\x89\\x49\\xc9\\x29\\xa9\\x69\\xe9\\x19\\x99\\x59\\xd9\\x39\\xb9\\x79\\xf9\" +\n\t\"\\x05\\x85\\x45\\xc5\\x25\\xa5\\x65\\xe5\\x15\\x95\\x55\\xd5\\x35\\xb5\\x75\\xf5\" +\n\t\"\\x0d\\x8d\\x4d\\xcd\\x2d\\xad\\x6d\\xed\\x1d\\x9d\\x5d\\xdd\\x3d\\xbd\\x7d\\xfd\" +\n\t\"\\x03\\x83\\x43\\xc3\\x23\\xa3\\x63\\xe3\\x13\\x93\\x53\\xd3\\x33\\xb3\\x73\\xf3\" +\n\t\"\\x0b\\x8b\\x4b\\xcb\\x2b\\xab\\x6b\\xeb\\x1b\\x9b\\x5b\\xdb\\x3b\\xbb\\x7b\\xfb\" +\n\t\"\\x07\\x87\\x47\\xc7\\x27\\xa7\\x67\\xe7\\x17\\x97\\x57\\xd7\\x37\\xb7\\x77\\xf7\" +\n\t\"\\x0f\\x8f\\x4f\\xcf\\x2f\\xaf\\x6f\\xef\\x1f\\x9f\\x5f\\xdf\\x3f\\xbf\\x7f\\xff\"\n\nconst len8tab = \"\" +\n\t\"\\x00\\x01\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x04\\x04\\x04\\x04\\x04\\x04\\x04\" +\n\t\"\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\"\n" + }, + { + "name": "bitwise.gno", + "body": "// bitwise contains bitwise operations for Uint instances.\n// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting.\n// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer.\npackage uint256\n\n// Or sets z = x | y and returns z.\nfunc (z *Uint) Or(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] | y.arr[0]\n\tz.arr[1] = x.arr[1] | y.arr[1]\n\tz.arr[2] = x.arr[2] | y.arr[2]\n\tz.arr[3] = x.arr[3] | y.arr[3]\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Uint) And(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026 y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026 y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026 y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026 y.arr[3]\n\treturn z\n}\n\n// Not sets z = ^x and returns z.\nfunc (z *Uint) Not(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0]\n\treturn z\n}\n\n// AndNot sets z = x \u0026^ y and returns z.\nfunc (z *Uint) AndNot(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026^ y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026^ y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026^ y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026^ y.arr[3]\n\treturn z\n}\n\n// Xor sets z = x ^ y and returns z.\nfunc (z *Uint) Xor(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] ^ y.arr[0]\n\tz.arr[1] = x.arr[1] ^ y.arr[1]\n\tz.arr[2] = x.arr[2] ^ y.arr[2]\n\tz.arr[3] = x.arr[3] ^ y.arr[3]\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Uint) Lsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.lsh64(x)\n\t\tcase 128:\n\t\t\treturn z.lsh128(x)\n\t\tcase 192:\n\t\t\treturn z.lsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.lsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.lsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.lsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[0] \u003e\u003e (64 - n)\n\tz.arr[0] = z.arr[0] \u003c\u003c n\n\nsh64:\n\tb = z.arr[1] \u003e\u003e (64 - n)\n\tz.arr[1] = (z.arr[1] \u003c\u003c n) | a\n\nsh128:\n\ta = z.arr[2] \u003e\u003e (64 - n)\n\tz.arr[2] = (z.arr[2] \u003c\u003c n) | b\n\nsh192:\n\tz.arr[3] = (z.arr[3] \u003c\u003c n) | a\n\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) Rsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.rsh64(x)\n\t\tcase 128:\n\t\t\treturn z.rsh128(x)\n\t\tcase 192:\n\t\t\treturn z.rsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.rsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.rsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.rsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[3] \u003c\u003c (64 - n)\n\tz.arr[3] = z.arr[3] \u003e\u003e n\n\nsh64:\n\tb = z.arr[2] \u003c\u003c (64 - n)\n\tz.arr[2] = (z.arr[2] \u003e\u003e n) | a\n\nsh128:\n\ta = z.arr[1] \u003c\u003c (64 - n)\n\tz.arr[1] = (z.arr[1] \u003e\u003e n) | b\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\n// SRsh (Signed/Arithmetic right shift)\n// considers z to be a signed integer, during right-shift\n// and sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) SRsh(x *Uint, n uint) *Uint {\n\t// If the MSB is 0, SRsh is same as Rsh.\n\tif !x.isBitSet(255) {\n\t\treturn z.Rsh(x, n)\n\t}\n\tif n%64 == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.srsh64(x)\n\t\tcase 128:\n\t\t\treturn z.srsh128(x)\n\t\tcase 192:\n\t\t\treturn z.srsh192(x)\n\t\tdefault:\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t}\n\tvar a uint64 = MaxUint64 \u003c\u003c (64 - n%64)\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t\tz.srsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.srsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.srsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\tz.arr[3], a = (z.arr[3]\u003e\u003en)|a, z.arr[3]\u003c\u003c(64-n)\n\nsh64:\n\tz.arr[2], a = (z.arr[2]\u003e\u003en)|a, z.arr[2]\u003c\u003c(64-n)\n\nsh128:\n\tz.arr[1], a = (z.arr[1]\u003e\u003en)|a, z.arr[1]\u003c\u003c(64-n)\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\nfunc (z *Uint) lsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0\n\treturn z\n}\n\nfunc (z *Uint) lsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0\n\treturn z\n}\n\nfunc (z *Uint) lsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0\n\treturn z\n}\n\nfunc (z *Uint) rsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) rsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) rsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3]\n\treturn z\n}\n\nfunc (z *Uint) srsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) srsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) srsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3]\n\treturn z\n}\n" + }, + { + "name": "bitwise_test.gno", + "body": "package uint256\n\nimport \"testing\"\n\ntype logicOpTest struct {\n\tname string\n\tx Uint\n\ty Uint\n\twant Uint\n}\n\nfunc TestOr(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Or(\u0026tc.x, \u0026tc.y)\n\t\t\tif *res != tc.want {\n\t\t\t\tt.Errorf(\"Or(%s, %s) = %s, want %s\", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := new(Uint).And(\u0026tc.x, \u0026tc.y)\n\t\t\tif *res != tc.want {\n\t\t\t\tt.Errorf(\"And(%s, %s) = %s, want %s\", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNot(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx Uint\n\t\twant Uint\n\t}{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Not(\u0026tc.x)\n\t\t\tif *res != tc.want {\n\t\t\t\tt.Errorf(\"Not(%s) = %s, want %s\", tc.x.ToString(), res.ToString(), (tc.want).ToString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAndNot(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := new(Uint).AndNot(\u0026tc.x, \u0026tc.y)\n\t\t\tif *res != tc.want {\n\t\t\t\tt.Errorf(\"AndNot(%s, %s) = %s, want %s\", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestXor(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Xor(\u0026tc.x, \u0026tc.y)\n\t\t\tif *res != tc.want {\n\t\t\t\tt.Errorf(\"Xor(%s, %s) = %s, want %s\", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 64, \"18446744073709551616\"},\n\t\t{\"1\", 128, \"340282366920938463463374607431768211456\"},\n\t\t{\"1\", 192, \"6277101735386680763835789423207666416102355444464034512896\"},\n\t\t{\"1\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"1\", 256, \"0\"},\n\t\t{\"31337\", 0, \"31337\"},\n\t\t{\"31337\", 1, \"62674\"},\n\t\t{\"31337\", 64, \"578065619037836218990592\"},\n\t\t{\"31337\", 128, \"10663428532201448629551770073089320442396672\"},\n\t\t{\"31337\", 192, \"196705537081812415096322133155058642481399512563169449530621952\"},\n\t\t{\"31337\", 193, \"393411074163624830192644266310117284962799025126338899061243904\"},\n\t\t{\"31337\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"31337\", 256, \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Lsh(x, tc.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %s, want %s\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"0\"},\n\t\t{\"1\", 64, \"0\"},\n\t\t{\"1\", 128, \"0\"},\n\t\t{\"1\", 192, \"0\"},\n\t\t{\"1\", 255, \"0\"},\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"1\"},\n\t\t{\"6277101735386680763835789423207666416102355444464034512896\", 192, \"1\"},\n\t\t{\"340282366920938463463374607431768211456\", 128, \"1\"},\n\t\t{\"18446744073709551616\", 64, \"1\"},\n\t\t{\"393411074163624830192644266310117284962799025126338899061243904\", 193, \"31337\"},\n\t\t{\"196705537081812415096322133155058642481399512563169449530621952\", 192, \"31337\"},\n\t\t{\"10663428532201448629551770073089320442396672\", 128, \"31337\"},\n\t\t{\"578065619037836218990592\", 64, \"31337\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := \u0026Uint{}\n\t\tgot.Rsh(x, tc.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %s, want %s\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n" + }, + { + "name": "cmp.gno", + "body": "// cmp (or, comparisons) includes methods for comparing Uint instances.\n// These comparison functions cover a range of operations including equality checks, less than/greater than\n// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical\n// decision making based on Uint values.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Cmp compares z and x and returns:\n//\n//\t-1 if z \u003c x\n//\t 0 if z == x\n//\t+1 if z \u003e x\nfunc (z *Uint) Cmp(x *Uint) (r int) {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\td0, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\td1, carry := bits.Sub64(z.arr[1], x.arr[1], carry)\n\td2, carry := bits.Sub64(z.arr[2], x.arr[2], carry)\n\td3, carry := bits.Sub64(z.arr[3], x.arr[3], carry)\n\tif carry == 1 {\n\t\treturn -1\n\t}\n\tif d0|d1|d2|d3 == 0 {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\n// IsZero returns true if z == 0\nfunc (z *Uint) IsZero() bool {\n\treturn (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Sign returns:\n//\n//\t-1 if z \u003c 0\n//\t 0 if z == 0\n//\t+1 if z \u003e 0\n//\n// Where z is interpreted as a two's complement signed number\nfunc (z *Uint) Sign() int {\n\tif z.IsZero() {\n\t\treturn 0\n\t}\n\tif z.arr[3] \u003c 0x8000000000000000 {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// LtUint64 returns true if z is smaller than n\nfunc (z *Uint) LtUint64(n uint64) bool {\n\treturn z.arr[0] \u003c n \u0026\u0026 (z.arr[1]|z.arr[2]|z.arr[3]) == 0\n}\n\n// GtUint64 returns true if z is larger than n\nfunc (z *Uint) GtUint64(n uint64) bool {\n\treturn z.arr[0] \u003e n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Uint) Lt(x *Uint) bool {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\t_, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\t_, carry = bits.Sub64(z.arr[1], x.arr[1], carry)\n\t_, carry = bits.Sub64(z.arr[2], x.arr[2], carry)\n\t_, carry = bits.Sub64(z.arr[3], x.arr[3], carry)\n\n\treturn carry != 0\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Uint) Gt(x *Uint) bool {\n\treturn x.Lt(z)\n}\n\n// Lte returns true if z \u003c= x\nfunc (z *Uint) Lte(x *Uint) bool {\n\tcond1 := z.Lt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Gte returns true if z \u003e= x\nfunc (z *Uint) Gte(x *Uint) bool {\n\tcond1 := z.Gt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Eq returns true if z == x\nfunc (z *Uint) Eq(x *Uint) bool {\n\treturn (z.arr[0] == x.arr[0]) \u0026\u0026 (z.arr[1] == x.arr[1]) \u0026\u0026 (z.arr[2] == x.arr[2]) \u0026\u0026 (z.arr[3] == x.arr[3])\n}\n\n// Neq returns true if z != x\nfunc (z *Uint) Neq(x *Uint) bool {\n\treturn !z.Eq(x)\n}\n\n// Sgt interprets z and x as signed integers, and returns\n// true if z \u003e x\nfunc (z *Uint) Sgt(x *Uint) bool {\n\tzSign := z.Sign()\n\txSign := x.Sign()\n\n\tswitch {\n\tcase zSign \u003e= 0 \u0026\u0026 xSign \u003c 0:\n\t\treturn true\n\tcase zSign \u003c 0 \u0026\u0026 xSign \u003e= 0:\n\t\treturn false\n\tdefault:\n\t\treturn z.Gt(x)\n\t}\n}\n" + }, + { + "name": "cmp_test.gno", + "body": "package uint256\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"10\", \"10\", 0},\n\t\t{\"10\", \"11\", -1},\n\t\t{\"11\", \"10\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsZero()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint64\n\t\twant bool\n\t}{\n\t\t{\"0\", 1, true},\n\t\t{\"1\", 0, false},\n\t\t{\"10\", 10, false},\n\t\t{\"0xffffffffffffffff\", 0, false},\n\t\t{\"0x10000000000000000\", 10000000000000000, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar x *Uint\n\t\tvar err error\n\n\t\tif strings.HasPrefix(tc.x, \"0x\") {\n\t\t\tx, err = FromHex(tc.x)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tx, err = FromDecimal(tc.x)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tgot := x.LtUint64(tc.y)\n\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"LtUint64(%s, %d) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSGT(t *testing.T) {\n\tx := MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\ty := MustFromHex(\"0x0\")\n\tactual := x.Sgt(y)\n\tif actual {\n\t\tt.Fatalf(\"Expected %v false\", actual)\n\t}\n\n\tx = MustFromHex(\"0x0\")\n\ty = MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\tactual = x.Sgt(y)\n\tif !actual {\n\t\tt.Fatalf(\"Expected %v true\", actual)\n\t}\n}\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twant bool\n\t}{\n\t\t{\"0xffffffffffffffff\", \"18446744073709551615\", true},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\", true},\n\t\t{\"0\", \"0\", true},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor i, tc := range tests {\n\t\tvar x *Uint\n\t\tvar err error\n\n\t\tif strings.HasPrefix(tc.x, \"0x\") {\n\t\t\tx, err = FromHex(tc.x)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tx, err = FromDecimal(tc.x)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n" + }, + { + "name": "conversion.gno", + "body": "// conversions contains methods for converting Uint instances to other types and vice versa.\n// This includes conversions to and from basic types such as uint64 and int32, as well as string representations\n// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats.\npackage uint256\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Uint) Uint64() uint64 {\n\treturn z.arr[0]\n}\n\n// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred\nfunc (z *Uint) Uint64WithOverflow() (uint64, bool) {\n\treturn z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0\n}\n\n// SetUint64 sets z to the value x\nfunc (z *Uint) SetUint64(x uint64) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x\n\treturn z\n}\n\n// IsUint64 reports whether z can be represented as a uint64.\nfunc (z *Uint) IsUint64() bool {\n\treturn (z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Dec returns the decimal representation of z.\nfunc (z *Uint) Dec() string {\n\tif z.IsZero() {\n\t\treturn \"0\"\n\t}\n\tif z.IsUint64() {\n\t\treturn strconv.FormatUint(z.Uint64(), 10)\n\t}\n\n\t// The max uint64 value being 18446744073709551615, the largest\n\t// power-of-ten below that is 10000000000000000000.\n\t// When we do a DivMod using that number, the remainder that we\n\t// get back is the lower part of the output.\n\t//\n\t// The ascii-output of remainder will never exceed 19 bytes (since it will be\n\t// below 10000000000000000000).\n\t//\n\t// Algorithm example using 100 as divisor\n\t//\n\t// 12345 % 100 = 45 (rem)\n\t// 12345 / 100 = 123 (quo)\n\t// -\u003e output '45', continue iterate on 123\n\tvar (\n\t\t// out is 98 bytes long: 78 (max size of a string without leading zeroes,\n\t\t// plus slack so we can copy 19 bytes every iteration).\n\t\t// We init it with zeroes, because when strconv appends the ascii representations,\n\t\t// it will omit leading zeroes.\n\t\tout = []byte(\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\t\tdivisor = NewUint(10000000000000000000) // 20 digits\n\t\ty = new(Uint).Set(z) // copy to avoid modifying z\n\t\tpos = len(out) // position to write to\n\t\tbuf = make([]byte, 0, 19) // buffer to write uint64:s to\n\t)\n\tfor {\n\t\t// Obtain Q and R for divisor\n\t\tvar quot Uint\n\t\trem := udivrem(quot.arr[:], y.arr[:], divisor)\n\t\ty.Set(\u0026quot) // Set Q for next loop\n\t\t// Convert the R to ascii representation\n\t\tbuf = strconv.AppendUint(buf[:0], rem.Uint64(), 10)\n\t\t// Copy in the ascii digits\n\t\tcopy(out[pos-len(buf):], buf)\n\t\tif y.IsZero() {\n\t\t\tbreak\n\t\t}\n\t\t// Move 19 digits left\n\t\tpos -= 19\n\t}\n\t// skip leading zeroes by only using the 'used size' of buf\n\treturn string(out[pos-len(buf):])\n}\n\nfunc (z *Uint) Scan(src interface{}) error {\n\tif src == nil {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tswitch src := src.(type) {\n\tcase string:\n\t\treturn z.scanScientificFromString(src)\n\tcase []byte:\n\t\treturn z.scanScientificFromString(string(src))\n\t}\n\treturn errors.New(\"default // unsupported type: can't convert to uint256.Uint\")\n}\n\nfunc (z *Uint) scanScientificFromString(src string) error {\n\tif len(src) == 0 {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tidx := strings.IndexByte(src, 'e')\n\tif idx == -1 {\n\t\treturn z.SetFromDecimal(src)\n\t}\n\tif err := z.SetFromDecimal(src[:idx]); err != nil {\n\t\treturn err\n\t}\n\tif src[(idx+1):] == \"0\" {\n\t\treturn nil\n\t}\n\texp := new(Uint)\n\tif err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {\n\t\treturn err\n\t}\n\tif exp.GtUint64(77) { // 10**78 is larger than 2**256\n\t\treturn ErrBig256Range\n\t}\n\texp.Exp(NewUint(10), exp)\n\tif _, overflow := z.MulOverflow(z, exp); overflow {\n\t\treturn ErrBig256Range\n\t}\n\treturn nil\n}\n\n// ToString returns the decimal string representation of z. It returns an empty string if z is nil.\n// OBS: doesn't exist from holiman's uint256\nfunc (z *Uint) ToString() string {\n\tif z == nil {\n\t\treturn \"\"\n\t}\n\n\treturn z.Dec()\n}\n\n// MarshalJSON implements json.Marshaler.\n// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible\n// with big.Uint: big.Uint marshals into JSON 'native' numeric format.\n//\n// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large\n// integer space. Thus, U256 uses string-format, which is not compatible with\n// big.int (big.Uint refuses to unmarshal a string representation).\nfunc (z *Uint) MarshalJSON() ([]byte, error) {\n\treturn []byte(`\"` + z.Dec() + `\"`), nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either\n// - Quoted string: either hexadecimal OR decimal\n// - Not quoted string: only decimal\nfunc (z *Uint) UnmarshalJSON(input []byte) error {\n\tif len(input) \u003c 2 || input[0] != '\"' || input[len(input)-1] != '\"' {\n\t\t// if not quoted, it must be decimal\n\t\treturn z.fromDecimal(string(input))\n\t}\n\treturn z.UnmarshalText(input[1 : len(input)-1])\n}\n\n// MarshalText implements encoding.TextMarshaler\n// MarshalText marshals using the decimal representation (compatible with big.Uint)\nfunc (z *Uint) MarshalText() ([]byte, error) {\n\treturn []byte(z.Dec()), nil\n}\n\n// UnmarshalText implements encoding.TextUnmarshaler. This method\n// can unmarshal either hexadecimal or decimal.\n// - For hexadecimal, the input _must_ be prefixed with 0x or 0X\nfunc (z *Uint) UnmarshalText(input []byte) error {\n\tif len(input) \u003e= 2 \u0026\u0026 input[0] == '0' \u0026\u0026 (input[1] == 'x' || input[1] == 'X') {\n\t\treturn z.fromHex(string(input))\n\t}\n\treturn z.fromDecimal(string(input))\n}\n\n// SetBytes interprets buf as the bytes of a big-endian unsigned\n// integer, sets z to that value, and returns z.\n// If buf is larger than 32 bytes, the last 32 bytes is used.\nfunc (z *Uint) SetBytes(buf []byte) *Uint {\n\tswitch l := len(buf); l {\n\tcase 0:\n\t\tz.Clear()\n\tcase 1:\n\t\tz.SetBytes1(buf)\n\tcase 2:\n\t\tz.SetBytes2(buf)\n\tcase 3:\n\t\tz.SetBytes3(buf)\n\tcase 4:\n\t\tz.SetBytes4(buf)\n\tcase 5:\n\t\tz.SetBytes5(buf)\n\tcase 6:\n\t\tz.SetBytes6(buf)\n\tcase 7:\n\t\tz.SetBytes7(buf)\n\tcase 8:\n\t\tz.SetBytes8(buf)\n\tcase 9:\n\t\tz.SetBytes9(buf)\n\tcase 10:\n\t\tz.SetBytes10(buf)\n\tcase 11:\n\t\tz.SetBytes11(buf)\n\tcase 12:\n\t\tz.SetBytes12(buf)\n\tcase 13:\n\t\tz.SetBytes13(buf)\n\tcase 14:\n\t\tz.SetBytes14(buf)\n\tcase 15:\n\t\tz.SetBytes15(buf)\n\tcase 16:\n\t\tz.SetBytes16(buf)\n\tcase 17:\n\t\tz.SetBytes17(buf)\n\tcase 18:\n\t\tz.SetBytes18(buf)\n\tcase 19:\n\t\tz.SetBytes19(buf)\n\tcase 20:\n\t\tz.SetBytes20(buf)\n\tcase 21:\n\t\tz.SetBytes21(buf)\n\tcase 22:\n\t\tz.SetBytes22(buf)\n\tcase 23:\n\t\tz.SetBytes23(buf)\n\tcase 24:\n\t\tz.SetBytes24(buf)\n\tcase 25:\n\t\tz.SetBytes25(buf)\n\tcase 26:\n\t\tz.SetBytes26(buf)\n\tcase 27:\n\t\tz.SetBytes27(buf)\n\tcase 28:\n\t\tz.SetBytes28(buf)\n\tcase 29:\n\t\tz.SetBytes29(buf)\n\tcase 30:\n\t\tz.SetBytes30(buf)\n\tcase 31:\n\t\tz.SetBytes31(buf)\n\tdefault:\n\t\tz.SetBytes32(buf[l-32:])\n\t}\n\treturn z\n}\n\n// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short\nfunc (z *Uint) SetBytes1(in []byte) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(in[0])\n\treturn z\n}\n\n// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short\nfunc (z *Uint) SetBytes2(in []byte) *Uint {\n\t_ = in[1] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\treturn z\n}\n\n// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short\nfunc (z *Uint) SetBytes3(in []byte) *Uint {\n\t_ = in[2] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\treturn z\n}\n\n// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short\nfunc (z *Uint) SetBytes4(in []byte) *Uint {\n\t_ = in[3] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\treturn z\n}\n\n// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short\nfunc (z *Uint) SetBytes5(in []byte) *Uint {\n\t_ = in[4] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint40(in[0:5])\n\treturn z\n}\n\n// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short\nfunc (z *Uint) SetBytes6(in []byte) *Uint {\n\t_ = in[5] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint48(in[0:6])\n\treturn z\n}\n\n// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short\nfunc (z *Uint) SetBytes7(in []byte) *Uint {\n\t_ = in[6] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint56(in[0:7])\n\treturn z\n}\n\n// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short\nfunc (z *Uint) SetBytes8(in []byte) *Uint {\n\t_ = in[7] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = binary.BigEndian.Uint64(in[0:8])\n\treturn z\n}\n\n// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short\nfunc (z *Uint) SetBytes9(in []byte) *Uint {\n\t_ = in[8] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(in[0])\n\tz.arr[0] = binary.BigEndian.Uint64(in[1:9])\n\treturn z\n}\n\n// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short\nfunc (z *Uint) SetBytes10(in []byte) *Uint {\n\t_ = in[9] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[2:10])\n\treturn z\n}\n\n// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short\nfunc (z *Uint) SetBytes11(in []byte) *Uint {\n\t_ = in[10] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[0] = binary.BigEndian.Uint64(in[3:11])\n\treturn z\n}\n\n// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short\nfunc (z *Uint) SetBytes12(in []byte) *Uint {\n\t_ = in[11] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[4:12])\n\treturn z\n}\n\n// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short\nfunc (z *Uint) SetBytes13(in []byte) *Uint {\n\t_ = in[12] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint40(in[0:5])\n\tz.arr[0] = binary.BigEndian.Uint64(in[5:13])\n\treturn z\n}\n\n// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short\nfunc (z *Uint) SetBytes14(in []byte) *Uint {\n\t_ = in[13] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint48(in[0:6])\n\tz.arr[0] = binary.BigEndian.Uint64(in[6:14])\n\treturn z\n}\n\n// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short\nfunc (z *Uint) SetBytes15(in []byte) *Uint {\n\t_ = in[14] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint56(in[0:7])\n\tz.arr[0] = binary.BigEndian.Uint64(in[7:15])\n\treturn z\n}\n\n// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short\nfunc (z *Uint) SetBytes16(in []byte) *Uint {\n\t_ = in[15] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[0] = binary.BigEndian.Uint64(in[8:16])\n\treturn z\n}\n\n// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short\nfunc (z *Uint) SetBytes17(in []byte) *Uint {\n\t_ = in[16] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(in[0])\n\tz.arr[1] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[0] = binary.BigEndian.Uint64(in[9:17])\n\treturn z\n}\n\n// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short\nfunc (z *Uint) SetBytes18(in []byte) *Uint {\n\t_ = in[17] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[0] = binary.BigEndian.Uint64(in[10:18])\n\treturn z\n}\n\n// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short\nfunc (z *Uint) SetBytes19(in []byte) *Uint {\n\t_ = in[18] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[1] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[0] = binary.BigEndian.Uint64(in[11:19])\n\treturn z\n}\n\n// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short\nfunc (z *Uint) SetBytes20(in []byte) *Uint {\n\t_ = in[19] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[0] = binary.BigEndian.Uint64(in[12:20])\n\treturn z\n}\n\n// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short\nfunc (z *Uint) SetBytes21(in []byte) *Uint {\n\t_ = in[20] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint40(in[0:5])\n\tz.arr[1] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[0] = binary.BigEndian.Uint64(in[13:21])\n\treturn z\n}\n\n// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short\nfunc (z *Uint) SetBytes22(in []byte) *Uint {\n\t_ = in[21] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint48(in[0:6])\n\tz.arr[1] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[0] = binary.BigEndian.Uint64(in[14:22])\n\treturn z\n}\n\n// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short\nfunc (z *Uint) SetBytes23(in []byte) *Uint {\n\t_ = in[22] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint56(in[0:7])\n\tz.arr[1] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[0] = binary.BigEndian.Uint64(in[15:23])\n\treturn z\n}\n\n// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short\nfunc (z *Uint) SetBytes24(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[1] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[0] = binary.BigEndian.Uint64(in[16:24])\n\treturn z\n}\n\n// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short\nfunc (z *Uint) SetBytes25(in []byte) *Uint {\n\t_ = in[24] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(in[0])\n\tz.arr[2] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[1] = binary.BigEndian.Uint64(in[9:17])\n\tz.arr[0] = binary.BigEndian.Uint64(in[17:25])\n\treturn z\n}\n\n// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short\nfunc (z *Uint) SetBytes26(in []byte) *Uint {\n\t_ = in[25] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[1] = binary.BigEndian.Uint64(in[10:18])\n\tz.arr[0] = binary.BigEndian.Uint64(in[18:26])\n\treturn z\n}\n\n// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short\nfunc (z *Uint) SetBytes27(in []byte) *Uint {\n\t_ = in[26] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[2] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[1] = binary.BigEndian.Uint64(in[11:19])\n\tz.arr[0] = binary.BigEndian.Uint64(in[19:27])\n\treturn z\n}\n\n// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short\nfunc (z *Uint) SetBytes28(in []byte) *Uint {\n\t_ = in[27] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[1] = binary.BigEndian.Uint64(in[12:20])\n\tz.arr[0] = binary.BigEndian.Uint64(in[20:28])\n\treturn z\n}\n\n// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short\nfunc (z *Uint) SetBytes29(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint40(in[0:5])\n\tz.arr[2] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[1] = binary.BigEndian.Uint64(in[13:21])\n\tz.arr[0] = binary.BigEndian.Uint64(in[21:29])\n\treturn z\n}\n\n// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short\nfunc (z *Uint) SetBytes30(in []byte) *Uint {\n\t_ = in[29] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint48(in[0:6])\n\tz.arr[2] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[1] = binary.BigEndian.Uint64(in[14:22])\n\tz.arr[0] = binary.BigEndian.Uint64(in[22:30])\n\treturn z\n}\n\n// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short\nfunc (z *Uint) SetBytes31(in []byte) *Uint {\n\t_ = in[30] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint56(in[0:7])\n\tz.arr[2] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[1] = binary.BigEndian.Uint64(in[15:23])\n\tz.arr[0] = binary.BigEndian.Uint64(in[23:31])\n\treturn z\n}\n\n// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in.\nfunc (z *Uint) SetBytes32(in []byte) *Uint {\n\t_ = in[31] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[2] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[1] = binary.BigEndian.Uint64(in[16:24])\n\tz.arr[0] = binary.BigEndian.Uint64(in[24:32])\n\treturn z\n}\n\n// Utility methods that are \"missing\" among the bigEndian.UintXX methods.\n\n// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order.\nfunc bigEndianUint40(b []byte) uint64 {\n\t_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[4]) | uint64(b[3])\u003c\u003c8 | uint64(b[2])\u003c\u003c16 | uint64(b[1])\u003c\u003c24 |\n\t\tuint64(b[0])\u003c\u003c32\n}\n\n// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order.\nfunc bigEndianUint56(b []byte) uint64 {\n\t_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[6]) | uint64(b[5])\u003c\u003c8 | uint64(b[4])\u003c\u003c16 | uint64(b[3])\u003c\u003c24 |\n\t\tuint64(b[2])\u003c\u003c32 | uint64(b[1])\u003c\u003c40 | uint64(b[0])\u003c\u003c48\n}\n\n// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order.\nfunc bigEndianUint48(b []byte) uint64 {\n\t_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[5]) | uint64(b[4])\u003c\u003c8 | uint64(b[3])\u003c\u003c16 | uint64(b[2])\u003c\u003c24 |\n\t\tuint64(b[1])\u003c\u003c32 | uint64(b[0])\u003c\u003c40\n}\n" + }, + { + "name": "conversion_test.gno", + "body": "package uint256\n\nimport \"testing\"\n\nfunc TestIsUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0x0\", true},\n\t\t{\"0x1\", true},\n\t\t{\"0x10\", true},\n\t\t{\"0xffffffffffffffff\", true},\n\t\t{\"0x10000000000000000\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromHex(tc.x)\n\t\tgot := x.IsUint64()\n\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsUint64(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestDec(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tz Uint\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"zero\",\n\t\t\tz: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"less than 20 digits\",\n\t\t\tz: Uint{arr: [4]uint64{1234567890, 0, 0, 0}},\n\t\t\twant: \"1234567890\",\n\t\t},\n\t\t{\n\t\t\tname: \"max possible value\",\n\t\t\tz: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: \"115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult := tc.z.Dec()\n\t\t\tif result != tc.want {\n\t\t\t\tt.Errorf(\"Dec(%v) = %s, want %s\", tc.z, result, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "error.gno", + "body": "package uint256\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrEmptyString = errors.New(\"empty hex string\")\n\tErrSyntax = errors.New(\"invalid hex string\")\n\tErrRange = errors.New(\"number out of range\")\n\tErrMissingPrefix = errors.New(\"hex string without 0x prefix\")\n\tErrEmptyNumber = errors.New(\"hex string \\\"0x\\\"\")\n\tErrLeadingZero = errors.New(\"hex number with leading zero digits\")\n\tErrBig256Range = errors.New(\"hex number \u003e 256 bits\")\n\tErrBadBufferLength = errors.New(\"bad ssz buffer length\")\n\tErrBadEncodedLength = errors.New(\"bad ssz encoded length\")\n\tErrInvalidBase = errors.New(\"invalid base\")\n\tErrInvalidBitSize = errors.New(\"invalid bit size\")\n)\n\ntype u256Error struct {\n\tfn string // function name\n\tinput string\n\terr error\n}\n\nfunc (e *u256Error) Error() string {\n\treturn e.fn + \": \" + e.input + \": \" + e.err.Error()\n}\n\nfunc (e *u256Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc errEmptyString(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyString}\n}\n\nfunc errSyntax(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrSyntax}\n}\n\nfunc errMissingPrefix(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrMissingPrefix}\n}\n\nfunc errEmptyNumber(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyNumber}\n}\n\nfunc errLeadingZero(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrLeadingZero}\n}\n\nfunc errRange(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrRange}\n}\n\nfunc errBig256Range(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBig256Range}\n}\n\nfunc errBadBufferLength(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBadBufferLength}\n}\n\nfunc errInvalidBase(fn string, base int) error {\n\treturn \u0026u256Error{fn: fn, input: string(base), err: ErrInvalidBase}\n}\n\nfunc errInvalidBitSize(fn string, bitSize int) error {\n\treturn \u0026u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize}\n}\n" + }, + { + "name": "mod.gno", + "body": "package uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Some utility functions\n\n// Reciprocal computes a 320-bit value representing 1/m\n//\n// Notes:\n// - specialized for m.arr[3] != 0, hence limited to 2^192 \u003c= m \u003c 2^256\n// - returns zero if m.arr[3] == 0\n// - starts with a 32-bit division, refines with newton-raphson iterations\nfunc Reciprocal(m *Uint) (mu [5]uint64) {\n\tif m.arr[3] == 0 {\n\t\treturn mu\n\t}\n\n\ts := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case\n\tp := 255 - s // floor(log_2(m)), m\u003e0\n\n\t// 0 or a power of 2?\n\n\t// Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0],\n\t// or at least two bits in m.arr[3]\n\n\tif m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]\u0026(m.arr[3]-1)) == 0 {\n\n\t\tmu[4] = ^uint64(0) \u003e\u003e uint(p\u002663)\n\t\tmu[3] = ^uint64(0)\n\t\tmu[2] = ^uint64(0)\n\t\tmu[1] = ^uint64(0)\n\t\tmu[0] = ^uint64(0)\n\n\t\treturn mu\n\t}\n\n\t// Maximise division precision by left-aligning divisor\n\n\tvar (\n\t\ty Uint // left-aligned copy of m\n\t\tr0 uint32 // estimate of 2^31/y\n\t)\n\n\ty.Lsh(m, uint(s)) // 1/2 \u003c y \u003c 1\n\n\t// Extract most significant 32 bits\n\n\tyh := uint32(y.arr[3] \u003e\u003e 32)\n\n\tif yh == 0x80000000 { // Avoid overflow in division\n\t\tr0 = 0xffffffff\n\t} else {\n\t\tr0, _ = bits.Div32(0x80000000, 0, yh)\n\t}\n\n\t// First iteration: 32 -\u003e 64\n\n\tt1 := uint64(r0) // 2^31/y\n\tt1 *= t1 // 2^62/y^2\n\tt1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y\n\n\tr1 := uint64(r0) \u003c\u003c 32 // 2^63/y\n\tr1 -= t1 // 2^63/y - 2^62/y = 2^62/y\n\tr1 *= 2 // 2^63/y\n\n\tif (r1 | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr1 = ^uint64(0)\n\t}\n\n\t// Second iteration: 64 -\u003e 128\n\n\t// square: 2^126/y^2\n\ta2h, a2l := bits.Mul64(r1, r1)\n\n\t// multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y\n\tb2h, _ := bits.Mul64(a2l, y.arr[2])\n\tc2h, c2l := bits.Mul64(a2l, y.arr[3])\n\td2h, d2l := bits.Mul64(a2h, y.arr[2])\n\te2h, e2l := bits.Mul64(a2h, y.arr[3])\n\n\tb2h, c := bits.Add64(b2h, c2l, 0)\n\te2l, c = bits.Add64(e2l, c2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t_, c = bits.Add64(b2h, d2l, 0)\n\te2l, c = bits.Add64(e2l, d2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t// subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y\n\tt2l, b := bits.Sub64(0, e2l, 0)\n\tt2h, _ := bits.Sub64(r1, e2h, b)\n\n\t// double: r2h:r2l = 2^127/y\n\tr2l, c := bits.Add64(t2l, t2l, 0)\n\tr2h, _ := bits.Add64(t2h, t2h, c)\n\n\tif (r2h | r2l | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr2h = ^uint64(0)\n\t\tr2l = ^uint64(0)\n\t}\n\n\t// Third iteration: 128 -\u003e 192\n\n\t// square r2 (keep 256 bits): 2^190/y^2\n\ta3h, a3l := bits.Mul64(r2l, r2l)\n\tb3h, b3l := bits.Mul64(r2l, r2h)\n\tc3h, c3l := bits.Mul64(r2h, r2h)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\t// multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y\n\n\tx0 := a3l\n\tx1 := a3h\n\tx2 := c3l\n\tx3 := c3h\n\n\tvar q0, q1, q2, q3, q4, t0 uint64\n\n\tq0, _ = bits.Mul64(x2, y.arr[0])\n\tq1, t0 = bits.Mul64(x3, y.arr[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x1, y.arr[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x3, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x3, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, y.arr[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x3, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\t// subtract: t3 = 2^191/y - 2^190/y = 2^190/y\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\tt3l, b := bits.Sub64(0, q2, b)\n\tt3m, b := bits.Sub64(r2l, q3, b)\n\tt3h, _ := bits.Sub64(r2h, q4, b)\n\n\t// double: r3 = 2^191/y\n\tr3l, c := bits.Add64(t3l, t3l, 0)\n\tr3m, c := bits.Add64(t3m, t3m, c)\n\tr3h, _ := bits.Add64(t3h, t3h, c)\n\n\t// Fourth iteration: 192 -\u003e 320\n\n\t// square r3\n\n\ta4h, a4l := bits.Mul64(r3l, r3l)\n\tb4h, b4l := bits.Mul64(r3l, r3m)\n\tc4h, c4l := bits.Mul64(r3l, r3h)\n\td4h, d4l := bits.Mul64(r3m, r3m)\n\te4h, e4l := bits.Mul64(r3m, r3h)\n\tf4h, f4l := bits.Mul64(r3h, r3h)\n\n\tb4h, c = bits.Add64(b4h, c4l, 0)\n\te4l, c = bits.Add64(e4l, c4h, c)\n\te4h, _ = bits.Add64(e4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\t// multiply by y\n\n\tx1, x0 = bits.Mul64(d4h, y.arr[0])\n\tx3, x2 = bits.Mul64(f4h, y.arr[0])\n\tt1, t0 = bits.Mul64(f4l, y.arr[0])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx3, _ = bits.Add64(x3, 0, c)\n\n\tt1, t0 = bits.Mul64(d4h, y.arr[1])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx4, t0 := bits.Mul64(f4h, y.arr[1])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[1])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[1])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[2])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[2])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx5, t0 := bits.Mul64(f4h, y.arr[2])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[2])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[2])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[3])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[3])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx6, t0 := bits.Mul64(f4h, y.arr[3])\n\tx5, c = bits.Add64(x5, t0, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\tt1, t0 = bits.Mul64(a4l, y.arr[3])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[3])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[3])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, c = bits.Add64(x5, t1, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\n\t// subtract\n\t_, b = bits.Sub64(0, x0, 0)\n\t_, b = bits.Sub64(0, x1, b)\n\tr4l, b := bits.Sub64(0, x2, b)\n\tr4k, b := bits.Sub64(0, x3, b)\n\tr4j, b := bits.Sub64(r3l, x4, b)\n\tr4i, b := bits.Sub64(r3m, x5, b)\n\tr4h, _ := bits.Sub64(r3h, x6, b)\n\n\t// Multiply candidate for 1/4y by y, with full precision\n\n\tx0 = r4l\n\tx1 = r4k\n\tx2 = r4j\n\tx3 = r4i\n\tx4 = r4h\n\n\tq1, q0 = bits.Mul64(x0, y.arr[0])\n\tq3, q2 = bits.Mul64(x2, y.arr[0])\n\tq5, q4 := bits.Mul64(x4, y.arr[0])\n\n\tt1, t0 = bits.Mul64(x1, y.arr[0])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[0])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq6, t0 := bits.Mul64(x4, y.arr[1])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[1])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[1])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq7, t0 := bits.Mul64(x4, y.arr[2])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[2])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq8, t0 := bits.Mul64(x4, y.arr[3])\n\tq7, c = bits.Add64(q7, t0, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq4, c = bits.Add64(q4, t0, 0)\n\tq5, c = bits.Add64(q5, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[3])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, c = bits.Add64(q7, t1, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\t// Final adjustment\n\n\t// subtract q from 1/4\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\t_, b = bits.Sub64(0, q2, b)\n\t_, b = bits.Sub64(0, q3, b)\n\t_, b = bits.Sub64(0, q4, b)\n\t_, b = bits.Sub64(0, q5, b)\n\t_, b = bits.Sub64(0, q6, b)\n\t_, b = bits.Sub64(0, q7, b)\n\t_, b = bits.Sub64(uint64(1)\u003c\u003c62, q8, b)\n\n\t// decrement the result\n\tx0, t := bits.Sub64(r4l, 1, 0)\n\tx1, t = bits.Sub64(r4k, 0, t)\n\tx2, t = bits.Sub64(r4j, 0, t)\n\tx3, t = bits.Sub64(r4i, 0, t)\n\tx4, _ = bits.Sub64(r4h, 0, t)\n\n\t// commit the decrement if the subtraction underflowed (reciprocal was too large)\n\tif b != 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\t// Shift to correct bit alignment, truncating excess bits\n\n\tp = (p \u0026 63) - 1\n\n\tx0, c = bits.Add64(r4l, r4l, 0)\n\tx1, c = bits.Add64(r4k, r4k, c)\n\tx2, c = bits.Add64(r4j, r4j, c)\n\tx3, c = bits.Add64(r4i, r4i, c)\n\tx4, _ = bits.Add64(r4h, r4h, c)\n\n\tif p \u003c 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t\tp = 0 // avoid negative shift below\n\t}\n\n\t{\n\t\tr := uint(p) // right shift\n\t\tl := uint(64 - r) // left shift\n\n\t\tx0 = (r4l \u003e\u003e r) | (r4k \u003c\u003c l)\n\t\tx1 = (r4k \u003e\u003e r) | (r4j \u003c\u003c l)\n\t\tx2 = (r4j \u003e\u003e r) | (r4i \u003c\u003c l)\n\t\tx3 = (r4i \u003e\u003e r) | (r4h \u003c\u003c l)\n\t\tx4 = (r4h \u003e\u003e r)\n\t}\n\n\tif p \u003e 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\tmu[0] = r4l\n\tmu[1] = r4k\n\tmu[2] = r4j\n\tmu[3] = r4i\n\tmu[4] = r4h\n\n\treturn mu\n}\n\n// reduce4 computes the least non-negative residue of x modulo m\n//\n// requires a four-word modulus (m.arr[3] \u003e 1) and its inverse (mu)\nfunc reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) {\n\t// NB: Most variable names in the comments match the pseudocode for\n\t// \tBarrett reduction in the Handbook of Applied Cryptography.\n\n\t// q1 = x/2^192\n\n\tx0 := x[3]\n\tx1 := x[4]\n\tx2 := x[5]\n\tx3 := x[6]\n\tx4 := x[7]\n\n\t// q2 = q1 * mu; q3 = q2 / 2^320\n\n\tvar q0, q1, q2, q3, q4, q5, t0, t1, c uint64\n\n\tq0, _ = bits.Mul64(x3, mu[0])\n\tq1, t0 = bits.Mul64(x4, mu[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x2, mu[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x4, mu[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x3, mu[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, mu[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x4, mu[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x1, mu[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x3, mu[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, mu[3])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, mu[3])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x4, mu[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, mu[4])\n\t_, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, mu[4])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq5, t0 = bits.Mul64(x4, mu[4])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[4])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[4])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\t// Drop the fractional part of q3\n\n\tq0 = q1\n\tq1 = q2\n\tq2 = q3\n\tq3 = q4\n\tq4 = q5\n\n\t// r1 = x mod 2^320\n\n\tx0 = x[0]\n\tx1 = x[1]\n\tx2 = x[2]\n\tx3 = x[3]\n\tx4 = x[4]\n\n\t// r2 = q3 * m mod 2^320\n\n\tvar r0, r1, r2, r3, r4 uint64\n\n\tr4, r3 = bits.Mul64(q0, m.arr[3])\n\t_, t0 = bits.Mul64(q1, m.arr[3])\n\tr4, _ = bits.Add64(r4, t0, 0)\n\n\tt1, r2 = bits.Mul64(q0, m.arr[2])\n\tr3, c = bits.Add64(r3, t1, 0)\n\t_, t0 = bits.Mul64(q2, m.arr[2])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[2])\n\tr3, c = bits.Add64(r3, t0, 0)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, r1 = bits.Mul64(q0, m.arr[1])\n\tr2, c = bits.Add64(r2, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[1])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[1])\n\tr2, c = bits.Add64(r2, t0, 0)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q3, m.arr[1])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, r0 = bits.Mul64(q0, m.arr[0])\n\tr1, c = bits.Add64(r1, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[0])\n\tr2, c = bits.Add64(r2, t0, c)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q4, m.arr[0])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[0])\n\tr1, c = bits.Add64(r1, t0, 0)\n\tr2, c = bits.Add64(r2, t1, c)\n\tt1, t0 = bits.Mul64(q3, m.arr[0])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\t// r = r1 - r2\n\n\tvar b uint64\n\n\tr0, b = bits.Sub64(x0, r0, 0)\n\tr1, b = bits.Sub64(x1, r1, b)\n\tr2, b = bits.Sub64(x2, r2, b)\n\tr3, b = bits.Sub64(x3, r3, b)\n\tr4, b = bits.Sub64(x4, r4, b)\n\n\t// if r\u003c0 then r+=m\n\n\tif b != 0 {\n\t\tr0, c = bits.Add64(r0, m.arr[0], 0)\n\t\tr1, c = bits.Add64(r1, m.arr[1], c)\n\t\tr2, c = bits.Add64(r2, m.arr[2], c)\n\t\tr3, c = bits.Add64(r3, m.arr[3], c)\n\t\tr4, _ = bits.Add64(r4, 0, c)\n\t}\n\n\t// while (r\u003e=m) r-=m\n\n\tfor {\n\t\t// q = r - m\n\t\tq0, b = bits.Sub64(r0, m.arr[0], 0)\n\t\tq1, b = bits.Sub64(r1, m.arr[1], b)\n\t\tq2, b = bits.Sub64(r2, m.arr[2], b)\n\t\tq3, b = bits.Sub64(r3, m.arr[3], b)\n\t\tq4, b = bits.Sub64(r4, 0, b)\n\n\t\t// if borrow break\n\t\tif b != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// r = q\n\t\tr4, r3, r2, r1, r0 = q4, q3, q2, q1, q0\n\t}\n\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0\n\n\treturn z\n}\n" + }, + { + "name": "uint256.gno", + "body": "// Ported from https://github.com/holiman/uint256\n// This package provides a 256-bit unsigned integer type, Uint256, and associated functions.\npackage uint256\n\nimport (\n\t\"errors\"\n\t\"math/bits\"\n)\n\nconst (\n\tMaxUint64 = 1\u003c\u003c64 - 1\n\tuintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63)\n)\n\n// Uint is represented as an array of 4 uint64, in little-endian order,\n// so that Uint[3] is the most significant, and Uint[0] is the least significant\ntype Uint struct {\n\tarr [4]uint64\n}\n\n// NewUint returns a new initialized Uint.\nfunc NewUint(val uint64) *Uint {\n\tz := \u0026Uint{arr: [4]uint64{val, 0, 0, 0}}\n\treturn z\n}\n\n// Zero returns a new Uint initialized to zero.\nfunc Zero() *Uint {\n\treturn NewUint(0)\n}\n\n// One returns a new Uint initialized to one.\nfunc One() *Uint {\n\treturn NewUint(1)\n}\n\n// SetAllOne sets all the bits of z to 1\nfunc (z *Uint) SetAllOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Uint) Set(x *Uint) *Uint {\n\t*z = *x\n\n\treturn z\n}\n\n// SetOne sets z to 1\nfunc (z *Uint) SetOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1\n\treturn z\n}\n\nconst twoPow256Sub1 = \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"\n\n// SetFromDecimal sets z from the given string, interpreted as a decimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method.\n// Notable differences:\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0\",\n// - (this method does not accept any negative input as valid))\nfunc (z *Uint) SetFromDecimal(s string) (err error) {\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\ts = s[1:]\n\t}\n\t// Remove any number of leading zeroes\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '0' {\n\t\tvar i int\n\t\tvar c rune\n\t\tfor i, c = range s {\n\t\t\tif c != '0' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ts = s[i:]\n\t}\n\tif len(s) \u003c len(twoPow256Sub1) {\n\t\treturn z.fromDecimal(s)\n\t}\n\tif len(s) == len(twoPow256Sub1) {\n\t\tif s \u003e twoPow256Sub1 {\n\t\t\treturn ErrBig256Range\n\t\t}\n\t\treturn z.fromDecimal(s)\n\t}\n\treturn ErrBig256Range\n}\n\n// FromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string. Numbers larger than 256 bits are not accepted.\nfunc FromDecimal(decimal string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromDecimal(decimal string) *Uint {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// multipliers holds the values that are needed for fromDecimal\nvar multipliers = [5]*Uint{\n\tnil, // represents first round, no multiplication needed\n\t{[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19\n\t{[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38\n\t{[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57\n\t{[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76\n}\n\n// fromDecimal is a helper function to only ever be called via SetFromDecimal\n// this function takes a string and chunks it up, calling ParseUint on it up to 5 times\n// these chunks are then multiplied by the proper power of 10, then added together.\nfunc (z *Uint) fromDecimal(bs string) error {\n\t// first clear the input\n\tz.Clear()\n\t// the maximum value of uint64 is 18446744073709551615, which is 20 characters\n\t// one less means that a string of 19 9's is always within the uint64 limit\n\tvar (\n\t\tnum uint64\n\t\terr error\n\t\tremaining = len(bs)\n\t)\n\tif remaining == 0 {\n\t\treturn errors.New(\"EOF\")\n\t}\n\t// We proceed in steps of 19 characters (nibbles), from least significant to most significant.\n\t// This means that the first (up to) 19 characters do not need to be multiplied.\n\t// In the second iteration, our slice of 19 characters needs to be multipleied\n\t// by a factor of 10^19. Et cetera.\n\tfor i, mult := range multipliers {\n\t\tif remaining \u003c= 0 {\n\t\t\treturn nil // Done\n\t\t} else if remaining \u003e 19 {\n\t\t\tnum, err = parseUint(bs[remaining-19:remaining], 10, 64)\n\t\t} else {\n\t\t\t// Final round\n\t\t\tnum, err = parseUint(bs, 10, 64)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// add that number to our running total\n\t\tif i == 0 {\n\t\t\tz.SetUint64(num)\n\t\t} else {\n\t\t\tbase := NewUint(num)\n\t\t\tz.Add(z, base.Mul(base, mult))\n\t\t}\n\t\t// Chop off another 19 characters\n\t\tif remaining \u003e 19 {\n\t\t\tbs = bs[0 : remaining-19]\n\t\t}\n\t\tremaining -= 19\n\t}\n\treturn nil\n}\n\n// Byte sets z to the value of the byte at position n,\n// with 'z' considered as a big-endian 32-byte integer\n// if 'n' \u003e 32, f is set to 0\n// Example: f = '5', n=31 =\u003e 5\nfunc (z *Uint) Byte(n *Uint) *Uint {\n\t// in z, z.arr[0] is the least significant\n\tif number, overflow := n.Uint64WithOverflow(); !overflow {\n\t\tif number \u003c 32 {\n\t\t\tnumber := z.arr[4-1-number/8]\n\t\t\toffset := (n.arr[0] \u0026 0x7) \u003c\u003c 3 // 8*(n.d % 8)\n\t\t\tz.arr[0] = (number \u0026 (0xff00000000000000 \u003e\u003e offset)) \u003e\u003e (56 - offset)\n\t\t\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\t\t\treturn z\n\t\t}\n\t}\n\n\treturn z.Clear()\n}\n\n// BitLen returns the number of bits required to represent z\nfunc (z *Uint) BitLen() int {\n\tswitch {\n\tcase z.arr[3] != 0:\n\t\treturn 192 + bits.Len64(z.arr[3])\n\tcase z.arr[2] != 0:\n\t\treturn 128 + bits.Len64(z.arr[2])\n\tcase z.arr[1] != 0:\n\t\treturn 64 + bits.Len64(z.arr[1])\n\tdefault:\n\t\treturn bits.Len64(z.arr[0])\n\t}\n}\n\n// ByteLen returns the number of bytes required to represent z\nfunc (z *Uint) ByteLen() int {\n\treturn (z.BitLen() + 7) / 8\n}\n\n// Clear sets z to 0\nfunc (z *Uint) Clear() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0\n\treturn z\n}\n\nconst (\n\t// hextable = \"0123456789abcdef\"\n\tbintable = \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n\tbadNibble = 0xff\n)\n\n// SetFromHex sets z from the given string, interpreted as a hexadecimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.\n// Notable differences:\n// - This method _require_ \"0x\" or \"0X\" prefix.\n// - This method does not accept zero-prefixed hex, e.g. \"0x0001\"\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0x0\",\n// - (this method does not accept any negative input as valid)\nfunc (z *Uint) SetFromHex(hex string) error {\n\treturn z.fromHex(hex)\n}\n\n// fromHex is the internal implementation of parsing a hex-string.\nfunc (z *Uint) fromHex(hex string) error {\n\tif err := checkNumberS(hex); err != nil {\n\t\treturn err\n\t}\n\tif len(hex) \u003e 66 {\n\t\treturn ErrBig256Range\n\t}\n\tz.Clear()\n\tend := len(hex)\n\tfor i := 0; i \u003c 4; i++ {\n\t\tstart := end - 16\n\t\tif start \u003c 2 {\n\t\t\tstart = 2\n\t\t}\n\t\tfor ri := start; ri \u003c end; ri++ {\n\t\t\tnib := bintable[hex[ri]]\n\t\t\tif nib == badNibble {\n\t\t\t\treturn ErrSyntax\n\t\t\t}\n\t\t\tz.arr[i] = z.arr[i] \u003c\u003c 4\n\t\t\tz.arr[i] += uint64(nib)\n\t\t}\n\t\tend = start\n\t}\n\treturn nil\n}\n\n// FromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string. The string is required to be '0x'-prefixed\n// Numbers larger than 256 bits are not accepted.\nfunc FromHex(hex string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromHex(hex string) *Uint {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// Clone creates a new Uint identical to z\nfunc (z *Uint) Clone() *Uint {\n\tvar x Uint\n\tx.arr[0] = z.arr[0]\n\tx.arr[1] = z.arr[1]\n\tx.arr[2] = z.arr[2]\n\tx.arr[3] = z.arr[3]\n\n\treturn \u0026x\n}\n" + }, + { + "name": "utils.gno", + "body": "package uint256\n\n// lower(c) is a lower-case letter if and only if\n// c is either that lower-case letter or the equivalent upper-case letter.\n// Instead of writing c == 'x' || c == 'X' one can write lower(c) == 'x'.\n// Note that lower of non-letters can produce other non-letters.\nfunc lower(c byte) byte {\n\treturn c | ('x' - 'X')\n}\n\n// underscoreOK reports whether the underscores in s are allowed.\n// Checking them in this one function lets all the parsers skip over them simply.\n// Underscore must appear only between digits or between a base prefix and a digit.\nfunc underscoreOK(s string) bool {\n\t// saw tracks the last character (class) we saw:\n\t// ^ for beginning of number,\n\t// 0 for a digit or base prefix,\n\t// _ for an underscore,\n\t// ! for none of the above.\n\tsaw := '^'\n\ti := 0\n\n\t// Optional sign.\n\tif len(s) \u003e= 1 \u0026\u0026 (s[0] == '-' || s[0] == '+') {\n\t\ts = s[1:]\n\t}\n\n\t// Optional base prefix.\n\thex := false\n\tif len(s) \u003e= 2 \u0026\u0026 s[0] == '0' \u0026\u0026 (lower(s[1]) == 'b' || lower(s[1]) == 'o' || lower(s[1]) == 'x') {\n\t\ti = 2\n\t\tsaw = '0' // base prefix counts as a digit for \"underscore as digit separator\"\n\t\thex = lower(s[1]) == 'x'\n\t}\n\n\t// Number proper.\n\tfor ; i \u003c len(s); i++ {\n\t\t// Digits are always okay.\n\t\tif '0' \u003c= s[i] \u0026\u0026 s[i] \u003c= '9' || hex \u0026\u0026 'a' \u003c= lower(s[i]) \u0026\u0026 lower(s[i]) \u003c= 'f' {\n\t\t\tsaw = '0'\n\t\t\tcontinue\n\t\t}\n\t\t// Underscore must follow digit.\n\t\tif s[i] == '_' {\n\t\t\tif saw != '0' {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tsaw = '_'\n\t\t\tcontinue\n\t\t}\n\t\t// Underscore must also be followed by digit.\n\t\tif saw == '_' {\n\t\t\treturn false\n\t\t}\n\t\t// Saw non-digit, non-underscore.\n\t\tsaw = '!'\n\t}\n\treturn saw != '_'\n}\n\nfunc checkNumberS(input string) error {\n\tconst fn = \"UnmarshalText\"\n\tl := len(input)\n\tif l == 0 {\n\t\treturn errEmptyString(fn, input)\n\t}\n\tif l \u003c 2 || input[0] != '0' ||\n\t\t(input[1] != 'x' \u0026\u0026 input[1] != 'X') {\n\t\treturn errMissingPrefix(fn, input)\n\t}\n\tif l == 2 {\n\t\treturn errEmptyNumber(fn, input)\n\t}\n\tif len(input) \u003e 3 \u0026\u0026 input[2] == '0' {\n\t\treturn errLeadingZero(fn, input)\n\t}\n\treturn nil\n}\n\n// ParseUint is like ParseUint but for unsigned numbers.\n//\n// A sign prefix is not permitted.\nfunc parseUint(s string, base int, bitSize int) (uint64, error) {\n\tconst fnParseUint = \"ParseUint\"\n\n\tif s == \"\" {\n\t\treturn 0, errSyntax(fnParseUint, s)\n\t}\n\n\tbase0 := base == 0\n\n\ts0 := s\n\tswitch {\n\tcase 2 \u003c= base \u0026\u0026 base \u003c= 36:\n\t\t// valid base; nothing to do\n\n\tcase base == 0:\n\t\t// Look for octal, hex prefix.\n\t\tbase = 10\n\t\tif s[0] == '0' {\n\t\t\tswitch {\n\t\t\tcase len(s) \u003e= 3 \u0026\u0026 lower(s[1]) == 'b':\n\t\t\t\tbase = 2\n\t\t\t\ts = s[2:]\n\t\t\tcase len(s) \u003e= 3 \u0026\u0026 lower(s[1]) == 'o':\n\t\t\t\tbase = 8\n\t\t\t\ts = s[2:]\n\t\t\tcase len(s) \u003e= 3 \u0026\u0026 lower(s[1]) == 'x':\n\t\t\t\tbase = 16\n\t\t\t\ts = s[2:]\n\t\t\tdefault:\n\t\t\t\tbase = 8\n\t\t\t\ts = s[1:]\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\treturn 0, errInvalidBase(fnParseUint, base)\n\t}\n\n\tif bitSize == 0 {\n\t\tbitSize = uintSize\n\t} else if bitSize \u003c 0 || bitSize \u003e 64 {\n\t\treturn 0, errInvalidBitSize(fnParseUint, bitSize)\n\t}\n\n\t// Cutoff is the smallest number such that cutoff*base \u003e maxUint64.\n\t// Use compile-time constants for common cases.\n\tvar cutoff uint64\n\tswitch base {\n\tcase 10:\n\t\tcutoff = MaxUint64/10 + 1\n\tcase 16:\n\t\tcutoff = MaxUint64/16 + 1\n\tdefault:\n\t\tcutoff = MaxUint64/uint64(base) + 1\n\t}\n\n\tmaxVal := uint64(1)\u003c\u003cuint(bitSize) - 1\n\n\tunderscores := false\n\tvar n uint64\n\tfor _, c := range []byte(s) {\n\t\tvar d byte\n\t\tswitch {\n\t\tcase c == '_' \u0026\u0026 base0:\n\t\t\tunderscores = true\n\t\t\tcontinue\n\t\tcase '0' \u003c= c \u0026\u0026 c \u003c= '9':\n\t\t\td = c - '0'\n\t\tcase 'a' \u003c= lower(c) \u0026\u0026 lower(c) \u003c= 'z':\n\t\t\td = lower(c) - 'a' + 10\n\t\tdefault:\n\t\t\treturn 0, errSyntax(fnParseUint, s0)\n\t\t}\n\n\t\tif d \u003e= byte(base) {\n\t\t\treturn 0, errSyntax(fnParseUint, s0)\n\t\t}\n\n\t\tif n \u003e= cutoff {\n\t\t\t// n*base overflows\n\t\t\treturn maxVal, errRange(fnParseUint, s0)\n\t\t}\n\t\tn *= uint64(base)\n\n\t\tn1 := n + uint64(d)\n\t\tif n1 \u003c n || n1 \u003e maxVal {\n\t\t\t// n+d overflows\n\t\t\treturn maxVal, errRange(fnParseUint, s0)\n\t\t}\n\t\tn = n1\n\t}\n\n\tif underscores \u0026\u0026 !underscoreOK(s0) {\n\t\treturn 0, errSyntax(fnParseUint, s0)\n\t}\n\n\treturn n, nil\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "int256", + "path": "gno.land/p/demo/int256", + "files": [ + { + "name": "LICENSE", + "body": "MIT License\n\nCopyright (c) 2023 Trịnh Đức Bảo Linh(Kevin)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE." + }, + { + "name": "README.md", + "body": "# Fixed size signed 256-bit math library\n\n1. This is a library specialized at replacing the big.Int library for math based on signed 256-bit types.\n2. It uses [uint256](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/uint256) as the underlying type.\n\nported from [mempooler/int256](https://github.com/mempooler/int256)\n" + }, + { + "name": "absolute.gno", + "body": "package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// Abs returns |z|\nfunc (z *Int) Abs() *uint256.Uint {\n\treturn z.abs.Clone()\n}\n\n// AbsGt returns true if |z| \u003e x, where x is a uint256\nfunc (z *Int) AbsGt(x *uint256.Uint) bool {\n\treturn z.abs.Gt(x)\n}\n\n// AbsLt returns true if |z| \u003c x, where x is a uint256\nfunc (z *Int) AbsLt(x *uint256.Uint) bool {\n\treturn z.abs.Lt(x)\n}\n" + }, + { + "name": "absolute_test.gno", + "body": "package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAbs(t *testing.T) {\n\ttests := []struct {\n\t\tx, want string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"-2\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Abs()\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Abs(%s) = %v, want %v\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"true\"},\n\t\t{\"-1\", \"0\", \"true\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsGt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsGt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"false\"},\n\t\t{\"-1\", \"0\", \"false\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"false\"},\n\t\t{\"-5\", \"10\", \"true\"},\n\t\t{\"31330\", \"31337\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsLt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsLt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n" + }, + { + "name": "arithmetic.gno", + "body": "package int256\n\nimport \"gno.land/p/demo/uint256\"\n\nfunc (z *Int) Add(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tneg := x.neg\n\n\tif x.neg == y.neg {\n\t\t// x + y == x + y\n\t\t// (-x) + (-y) == -(x + y)\n\t\tz.abs = z.abs.Add(x.abs, y.abs)\n\t} else {\n\t\t// x + (-y) == x - y == -(y - x)\n\t\t// (-x) + y == y - x == -(x - y)\n\t\tif x.abs.Cmp(y.abs) \u003e= 0 {\n\t\t\tz.abs = z.abs.Sub(x.abs, y.abs)\n\t\t} else {\n\t\t\tneg = !neg\n\t\t\tz.abs = z.abs.Sub(y.abs, x.abs)\n\t\t}\n\t}\n\tz.neg = neg // 0 has no sign\n\treturn z\n}\n\n// AddUint256 set z to the sum x + y, where y is a uint256, and returns z\nfunc (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tif x.abs.Gt(y) {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = false\n\t\t}\n\t} else {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDelta(z, x *uint256.Uint, y *Int) {\n\tif y.neg {\n\t\tz.Sub(x, y.abs)\n\t} else {\n\t\tz.Add(x, y.abs)\n\t}\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool {\n\tvar overflow bool\n\tif y.neg {\n\t\t_, overflow = z.SubOverflow(x, y.abs)\n\t} else {\n\t\t_, overflow = z.AddOverflow(x, y.abs)\n\t}\n\treturn overflow\n}\n\n// Sub sets z to the difference x-y and returns z.\nfunc (z *Int) Sub(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tneg := x.neg\n\tif x.neg != y.neg {\n\t\t// x - (-y) == x + y\n\t\t// (-x) - y == -(x + y)\n\t\tz.abs = z.abs.Add(x.abs, y.abs)\n\t} else {\n\t\t// x - y == x - y == -(y - x)\n\t\t// (-x) - (-y) == y - x == -(x - y)\n\t\tif x.abs.Cmp(y.abs) \u003e= 0 {\n\t\t\tz.abs = z.abs.Sub(x.abs, y.abs)\n\t\t} else {\n\t\t\tneg = !neg\n\t\t\tz.abs = z.abs.Sub(y.abs, x.abs)\n\t\t}\n\t}\n\tz.neg = neg // 0 has no sign\n\treturn z\n}\n\n// SubUint256 set z to the difference x - y, where y is a uint256, and returns z\nfunc (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = true\n\t} else {\n\t\tif x.abs.Lt(y) {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = false\n\t\t}\n\t}\n\treturn z\n}\n\n// Mul sets z to the product x*y and returns z.\nfunc (z *Int) Mul(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Mul(x.abs, y.abs)\n\tz.neg = x.neg != y.neg // 0 has no sign\n\treturn z\n}\n\n// MulUint256 sets z to the product x*y, where y is a uint256, and returns z\nfunc (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Mul(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Div sets z to the quotient x/y for y != 0 and returns z.\nfunc (z *Int) Div(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tz.abs.Div(x.abs, y.abs)\n\tif x.neg == y.neg {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = true\n\t}\n\treturn z\n}\n\n// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z\n// If y == 0, z is set to 0\nfunc (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Div(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Quo sets z to the quotient x/y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Quo implements truncated division (like Go); see QuoRem for more details.\nfunc (z *Int) Quo(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Div(x.abs, y.abs)\n\tz.neg = !(z.abs.IsZero()) \u0026\u0026 x.neg != y.neg // 0 has no sign\n\treturn z\n}\n\n// Rem sets z to the remainder x%y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Rem implements truncated modulus (like Go); see QuoRem for more details.\nfunc (z *Int) Rem(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs.Mod(x.abs, y.abs)\n\tz.neg = z.abs.Sign() \u003e 0 \u0026\u0026 x.neg // 0 has no sign\n\treturn z\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Int) Mod(x, y *Int) *Int {\n\tif x.neg {\n\t\tz.abs.Div(x.abs, y.abs)\n\t\tz.abs.Add(z.abs, one)\n\t\tz.abs.Mul(z.abs, y.abs)\n\t\tz.abs.Sub(z.abs, x.abs)\n\t\tz.abs.Mod(z.abs, y.abs)\n\t} else {\n\t\tz.abs.Mod(x.abs, y.abs)\n\t}\n\tz.neg = false\n\treturn z\n}\n" + }, + { + "name": "arithmetic_test.gno", + "body": "package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAdd(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t// NEGATIVE\n\t\t{\"-1\", \"1\", \"-0\"}, // TODO: remove negative sign for 0 ??\n\t\t{\"1\", \"-1\", \"0\"},\n\t\t{\"-1\", \"-1\", \"-2\"},\n\t\t{\"-1\", \"-2\", \"-3\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"3\", \"-1\", \"2\"},\n\t\t// OVERFLOW\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"-1\"},\n\t\t// OVERFLOW\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.AddUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"AddUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDelta(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"0\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\", \"1\"},\n\t\t{\"0\", \"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\", \"5\"},\n\t\t{\"5\", \"10\", \"-3\", \"7\"},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := uint256.FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tAddDelta(z, x, y)\n\n\t\tif z.Neq(want) {\n\t\t\tt.Errorf(\"AddDelta(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, z.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDeltaOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", \"0\", false},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tresult := AddDeltaOverflow(z, x, y)\n\t\tif result != tc.want {\n\t\t\tt.Errorf(\"AddDeltaOverflow(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, result, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"1\", \"-1\", \"2\"},\n\t\t{\"-1\", \"-1\", \"-0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{x: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", y: \"1\", want: \"-0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Sub(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSubUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"-1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"1\", \"2\", \"-1\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"-1\", \"3\", \"-4\"},\n\t\t// underflow\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"-0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"-1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"-2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.SubUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"SubUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"5\", \"3\", \"15\"},\n\t\t{\"-5\", \"3\", \"-15\"},\n\t\t{\"5\", \"-3\", \"-15\"},\n\t\t{\"0\", \"3\", \"0\"},\n\t\t{\"3\", \"0\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"2\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"-3\"},\n\t\t{\"3\", \"4\", \"12\"},\n\t\t{\"-3\", \"4\", \"-12\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.MulUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"MulUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"-0\"},\n\t\t{\"10\", \"0\", \"0\"},\n\t\t{\"10\", \"1\", \"10\"},\n\t\t{\"10\", \"-1\", \"-10\"},\n\t\t{\"-10\", \"0\", \"-0\"},\n\t\t{\"-10\", \"1\", \"-10\"},\n\t\t{\"-10\", \"-1\", \"10\"},\n\t\t{\"10\", \"-3\", \"-3\"},\n\t\t{\"10\", \"3\", \"3\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDivUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"0\"},\n\t\t{\"4\", \"3\", \"1\"},\n\t\t{\"25\", \"5\", \"5\"},\n\t\t{\"25\", \"4\", \"6\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.DivUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"DivUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestQuo(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"10\"},\n\t\t{\"10\", \"-1\", \"-10\"},\n\t\t{\"-10\", \"1\", \"-10\"},\n\t\t{\"-10\", \"-1\", \"10\"},\n\t\t{\"10\", \"-3\", \"-3\"},\n\t\t{\"10\", \"3\", \"3\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Quo(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Quo(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRem(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"-1\"},\n\t\t{\"-10\", \"-3\", \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rem(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rem(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"0\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"0\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"2\"},\n\t\t{\"-10\", \"-3\", \"2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n" + }, + { + "name": "bitwise.gno", + "body": "package int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\n// Or sets z = x | y and returns z.\nfunc (z *Int) Or(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) \u0026 (y-1)) == -(((x-1) \u0026 (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.And(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x | y == x | y\n\t\tz.abs = z.abs.Or(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\tif x.neg {\n\t\tx, y = y, x // | is symmetric\n\t}\n\n\t// x | (-y) == x | ^(y-1) == ^((y-1) \u0026^ x) == -(^((y-1) \u0026^ x) + 1)\n\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\tz.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one)\n\tz.neg = true // z cannot be zero if one of x or y is negative\n\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Int) And(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) \u0026 (-y) == ^(x-1) \u0026 ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.Or(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x \u0026 y == x \u0026 y\n\t\tz.abs = z.abs.And(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tif x.neg {\n\t\tx, y = y, x // \u0026 is symmetric\n\t}\n\n\t// x \u0026 (-y) == x \u0026 ^(y-1) == x \u0026^ (y-1)\n\ty1 := new(uint256.Uint).Sub(y.abs, uint256.One())\n\tz.abs = z.abs.AndNot(x.abs, y1)\n\tz.neg = false\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\n// OBS: Different from original implementation it was using math.Big\nfunc (z *Int) Rsh(x *Int, n uint) *Int {\n\tif !x.neg {\n\t\tz.abs.Rsh(x.abs, n)\n\t\tz.neg = x.neg\n\t\treturn z\n\t}\n\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tt := NewInt(0).Sub(FromUint256(x.abs), NewInt(1))\n\tt = t.Rsh(t, n)\n\n\t_tmp := t.Add(t, NewInt(1))\n\tz.abs = _tmp.Abs()\n\tz.neg = true\n\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Int) Lsh(x *Int, n uint) *Int {\n\tz.abs.Lsh(x.abs, n)\n\tz.neg = x.neg\n\treturn z\n}\n" + }, + { + "name": "bitwise_test.gno", + "body": "package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestOr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.Or(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"Or(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.And(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"And(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1024\", 0, \"1024\"},\n\t\t{\"1024\", 1, \"512\"},\n\t\t{\"1024\", 2, \"256\"},\n\t\t{\"1024\", 10, \"1\"},\n\t\t{\"1024\", 11, \"0\"},\n\t\t{\"18446744073709551615\", 0, \"18446744073709551615\"},\n\t\t{\"18446744073709551615\", 1, \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", 62, \"3\"},\n\t\t{\"18446744073709551615\", 63, \"1\"},\n\t\t{\"18446744073709551615\", 64, \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 0, \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 128, \"340282366920938463463374607431768211455\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 255, \"1\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 256, \"0\"},\n\t\t{\"-1024\", 0, \"-1024\"},\n\t\t{\"-1024\", 1, \"-512\"},\n\t\t{\"-1024\", 2, \"-256\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-9223372036854775808\", 0, \"-9223372036854775808\"},\n\t\t{\"-9223372036854775808\", 1, \"-4611686018427387904\"},\n\t\t{\"-9223372036854775808\", 62, \"-2\"},\n\t\t{\"-9223372036854775808\", 63, \"-1\"},\n\t\t{\"-9223372036854775808\", 64, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 0, \"-57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 1, \"-28948022309329048855892746252171976963317496166410141009864396001978282409984\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 253, \"-4\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 254, \"-2\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 256, \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 2, \"4\"},\n\t\t{\"2\", 0, \"2\"},\n\t\t{\"2\", 1, \"4\"},\n\t\t{\"2\", 2, \"8\"},\n\t\t{\"-2\", 0, \"-2\"},\n\t\t{\"-4\", 0, \"-4\"},\n\t\t{\"-8\", 0, \"-8\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Lsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n" + }, + { + "name": "cmp.gno", + "body": "package int256\n\n// Eq returns true if z == x\nfunc (z *Int) Eq(x *Int) bool {\n\treturn (z.neg == x.neg) \u0026\u0026 z.abs.Eq(x.abs)\n}\n\n// Neq returns true if z != x\nfunc (z *Int) Neq(x *Int) bool {\n\treturn !z.Eq(x)\n}\n\n// Cmp compares x and y and returns:\n//\n//\t-1 if x \u003c y\n//\t 0 if x == y\n//\t+1 if x \u003e y\nfunc (z *Int) Cmp(x *Int) (r int) {\n\t// x cmp y == x cmp y\n\t// x cmp (-y) == x\n\t// (-x) cmp y == y\n\t// (-x) cmp (-y) == -(x cmp y)\n\tswitch {\n\tcase z == x:\n\t\t// nothing to do\n\tcase z.neg == x.neg:\n\t\tr = z.abs.Cmp(x.abs)\n\t\tif z.neg {\n\t\t\tr = -r\n\t\t}\n\tcase z.neg:\n\t\tr = -1\n\tdefault:\n\t\tr = 1\n\t}\n\treturn\n}\n\n// IsZero returns true if z == 0\nfunc (z *Int) IsZero() bool {\n\treturn z.abs.IsZero()\n}\n\n// IsNeg returns true if z \u003c 0\nfunc (z *Int) IsNeg() bool {\n\treturn z.neg\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Int) Lt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t}\n\t}\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Int) Gt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t}\n\t}\n}\n\n// Clone creates a new Int identical to z\nfunc (z *Int) Clone() *Int {\n\treturn \u0026Int{z.abs.Clone(), z.neg}\n}\n" + }, + { + "name": "cmp_test.gno", + "body": "package int256\n\nimport \"testing\"\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", true},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", true},\n\t\t{\"-1\", \"-1\", true},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Neq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"-1\", \"0\", -1},\n\t\t{\"0\", \"-1\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"-0\", true},\n\t\t{\"1\", false},\n\t\t{\"-1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsZero()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", false},\n\t\t{\"-0\", true}, // TODO: should this be false?\n\t\t{\"1\", false},\n\t\t{\"-1\", true},\n\t\t{\"10\", false},\n\t\t{\"-10\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsNeg()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsNeg(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Lt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Lt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Gt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Gt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t}{\n\t\t{\"0\"},\n\t\t{\"-0\"},\n\t\t{\"1\"},\n\t\t{\"-1\"},\n\t\t{\"10\"},\n\t\t{\"-10\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty := x.Clone()\n\n\t\tif x.Cmp(y) != 0 {\n\t\t\tt.Errorf(\"Clone(%s) = %v, want %v\", tc.x, y, x)\n\t\t}\n\t}\n}\n" + }, + { + "name": "conversion.gno", + "body": "package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// SetInt64 sets z to x and returns z.\nfunc (z *Int) SetInt64(x int64) *Int {\n\tz.initiateAbs()\n\n\tneg := false\n\tif x \u003c 0 {\n\t\tneg = true\n\t\tx = -x\n\t}\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(uint64(x))\n\tz.neg = neg\n\treturn z\n}\n\n// SetUint64 sets z to x and returns z.\nfunc (z *Int) SetUint64(x uint64) *Int {\n\tz.initiateAbs()\n\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(x)\n\tz.neg = false\n\treturn z\n}\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Int) Uint64() uint64 {\n\treturn z.abs.Uint64()\n}\n\n// Int64 returns the lower 64-bits of z\nfunc (z *Int) Int64() int64 {\n\t_abs := z.abs.Clone()\n\n\tif z.neg {\n\t\treturn -int64(_abs.Uint64())\n\t}\n\treturn int64(_abs.Uint64())\n}\n\n// Neg sets z to -x and returns z.)\nfunc (z *Int) Neg(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = !x.neg\n\t}\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Int) Set(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tz.neg = x.neg\n\treturn z\n}\n\n// SetFromUint256 converts a uint256.Uint to Int and sets the value to z.\nfunc (z *Int) SetUint256(x *uint256.Uint) *Int {\n\tz.abs.Set(x)\n\tz.neg = false\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// ToString returns the decimal representation of z.\nfunc (z *Int) ToString() string {\n\tif z == nil {\n\t\tpanic(\"int256: nil pointer to ToString()\")\n\t}\n\n\tt := z.abs.Dec()\n\tif z.neg {\n\t\treturn \"-\" + t\n\t}\n\treturn t\n}\n" + }, + { + "name": "conversion_test.gno", + "body": "package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestSetInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx int64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t\t{-1, \"-1\"},\n\t\t{9223372036854775807, \"9223372036854775807\"},\n\t\t{-9223372036854775808, \"-9223372036854775808\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetInt64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetInt64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx uint64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetUint64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetUint64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant uint64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"9223372036854775808\", 9223372036854775808},\n\t\t{\"18446744073709551615\", 18446744073709551615},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", 1},\n\t\t{\"-18446744073709551615\", 18446744073709551615},\n\t\t{\"-18446744073709551616\", 0},\n\t\t{\"-18446744073709551617\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Uint64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", -1},\n\t\t{\"-9223372036854775808\", -9223372036854775808},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Int64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"-1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"9223372036854775807\", \"-9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Neg(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neg(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSet(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"-1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"-18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Set(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Set(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tgot := New()\n\n\t\tz := uint256.MustFromDecimal(tc.x)\n\t\tgot.SetUint256(z)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"SetUint256(%s) = %s, want %s\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n" + }, + { + "name": "int256.gno", + "body": "// This package provides a 256-bit signed integer type, Int, and associated functions.\npackage int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\nvar one = uint256.NewUint(1)\n\ntype Int struct {\n\tabs *uint256.Uint\n\tneg bool\n}\n\n// Zero returns a new Int set to 0.\nfunc Zero() *Int {\n\treturn NewInt(0)\n}\n\n// One returns a new Int set to 1.\nfunc One() *Int {\n\treturn NewInt(1)\n}\n\n// Sign returns:\n//\n//\t-1 if x \u003c 0\n//\t 0 if x == 0\n//\t+1 if x \u003e 0\nfunc (z *Int) Sign() int {\n\tz.initiateAbs()\n\n\tif z.abs.IsZero() {\n\t\treturn 0\n\t}\n\tif z.neg {\n\t\treturn -1\n\t}\n\treturn 1\n}\n\n// New returns a new Int set to 0.\nfunc New() *Int {\n\treturn \u0026Int{\n\t\tabs: new(uint256.Uint),\n\t}\n}\n\n// NewInt allocates and returns a new Int set to x.\nfunc NewInt(x int64) *Int {\n\treturn New().SetInt64(x)\n}\n\n// FromDecimal returns a new Int from a decimal string.\n// Returns a new Int and an error if the string is not a valid decimal.\nfunc FromDecimal(s string) (*Int, error) {\n\treturn new(Int).SetString(s)\n}\n\n// MustFromDecimal returns a new Int from a decimal string.\n// Panics if the string is not a valid decimal.\nfunc MustFromDecimal(s string) *Int {\n\tz, err := FromDecimal(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn z\n}\n\n// SetString sets s to the value of z and returns z and a boolean indicating success.\nfunc (z *Int) SetString(s string) (*Int, error) {\n\tneg := false\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\tneg = false\n\t\ts = s[1:]\n\t}\n\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '-' {\n\t\tneg = true\n\t\ts = s[1:]\n\t}\n\tvar (\n\t\tabs *uint256.Uint\n\t\terr error\n\t)\n\tabs, err = uint256.FromDecimal(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn \u0026Int{\n\t\tabs,\n\t\tneg,\n\t}, nil\n}\n\n// FromUint256 is a convenience-constructor from uint256.Uint.\n// Returns a new Int and whether overflow occurred.\n// OBS: If u is `nil`, this method returns `nil, false`\nfunc FromUint256(x *uint256.Uint) *Int {\n\tif x == nil {\n\t\treturn nil\n\t}\n\tz := Zero()\n\n\tz.SetUint256(x)\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// NilToZero sets z to 0 and return it if it's nil, otherwise it returns z\nfunc (z *Int) NilToZero() *Int {\n\tif z == nil {\n\t\treturn NewInt(0)\n\t}\n\treturn z\n}\n\n// initiateAbs sets default value for `z` or `z.abs` value if is nil\n// OBS: differs from mempooler int256. It checks not only `z.abs` but also `z`\nfunc (z *Int) initiateAbs() {\n\tif z == nil || z.abs == nil {\n\t\tz.abs = new(uint256.Uint)\n\t}\n}\n" + }, + { + "name": "int256_test.gno", + "body": "// ported from github.com/mempooler/int256\npackage int256\n\nimport \"testing\"\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"-1\", -1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tgot := z.Sign()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Sign(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "eisel_lemire", + "path": "gno.land/p/demo/json/eisel_lemire", + "files": [ + { + "name": "eisel_lemire.gno", + "body": "// Copyright 2020 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage eisel_lemire\n\n// This file implements the Eisel-Lemire ParseFloat algorithm, published in\n// 2020 and discussed extensively at\n// https://nigeltao.github.io/blog/2020/eisel-lemire.html\n//\n// The original C++ implementation is at\n// https://github.com/lemire/fast_double_parser/blob/644bef4306059d3be01a04e77d3cc84b379c596f/include/fast_double_parser.h#L840\n//\n// This Go re-implementation closely follows the C re-implementation at\n// https://github.com/google/wuffs/blob/ba3818cb6b473a2ed0b38ecfc07dbbd3a97e8ae7/internal/cgen/base/floatconv-submodule-code.c#L990\n//\n// Additional testing (on over several million test strings) is done by\n// https://github.com/nigeltao/parse-number-fxx-test-data/blob/5280dcfccf6d0b02a65ae282dad0b6d9de50e039/script/test-go-strconv.go\n\nimport (\n\t\"math\"\n\t\"math/bits\"\n)\n\nconst (\n\tfloat32ExponentBias = 127\n\tfloat64ExponentBias = 1023\n)\n\n// eiselLemire64 parses a floating-point number from its mantissa and exponent representation.\n// This implementation is based on the Eisel-Lemire ParseFloat algorithm, which is efficient\n// and precise for converting strings to floating-point numbers.\n//\n// Arguments:\n// man (uint64): The mantissa part of the floating-point number.\n// exp10 (int): The exponent part, representing the power of 10.\n// neg (bool): Indicates if the number is negative.\n//\n// Returns:\n// f (float64): The parsed floating-point number.\n// ok (bool): Indicates whether the parsing was successful.\n//\n// The function starts by handling special cases, such as zero mantissa.\n// It then checks if the exponent is within the allowed range.\n// After that, it normalizes the mantissa by left-shifting it to fill\n// the leading zeros. This is followed by the main algorithm logic that\n// converts the normalized mantissa and exponent into a 64-bit floating-point number.\n// The function returns this number along with a boolean indicating the success of the operation.\nfunc EiselLemire64(man uint64, exp10 int, neg bool) (f float64, ok bool) {\n\t// The terse comments in this function body refer to sections of the\n\t// https://nigeltao.github.io/blog/2020/eisel-lemire.html blog post.\n\n\t// Exp10 Range.\n\tif man == 0 {\n\t\tif neg {\n\t\t\tf = math.Float64frombits(0x80000000_00000000) // Negative zero.\n\t\t}\n\n\t\treturn f, true\n\t}\n\n\tif exp10 \u003c detailedPowersOfTenMinExp10 || detailedPowersOfTenMaxExp10 \u003c exp10 {\n\t\treturn 0, false\n\t}\n\n\t// Normalization.\n\tclz := bits.LeadingZeros64(man)\n\tman \u003c\u003c= uint(clz)\n\tretExp2 := uint64(217706*exp10\u003e\u003e16+64+float64ExponentBias) - uint64(clz)\n\n\t// Multiplication.\n\txHi, xLo := bits.Mul64(man, detailedPowersOfTen[exp10-detailedPowersOfTenMinExp10][1])\n\n\t// Wider Approximation.\n\tif xHi\u00260x1FF == 0x1FF \u0026\u0026 xLo+man \u003c man {\n\t\tyHi, yLo := bits.Mul64(man, detailedPowersOfTen[exp10-detailedPowersOfTenMinExp10][0])\n\t\tmergedHi, mergedLo := xHi, xLo+yHi\n\t\tif mergedLo \u003c xLo {\n\t\t\tmergedHi++\n\t\t}\n\n\t\tif mergedHi\u00260x1FF == 0x1FF \u0026\u0026 mergedLo+1 == 0 \u0026\u0026 yLo+man \u003c man {\n\t\t\treturn 0, false\n\t\t}\n\n\t\txHi, xLo = mergedHi, mergedLo\n\t}\n\n\t// Shifting to 54 Bits.\n\tmsb := xHi \u003e\u003e 63\n\tretMantissa := xHi \u003e\u003e (msb + 9)\n\tretExp2 -= 1 ^ msb\n\n\t// Half-way Ambiguity.\n\tif xLo == 0 \u0026\u0026 xHi\u00260x1FF == 0 \u0026\u0026 retMantissa\u00263 == 1 {\n\t\treturn 0, false\n\t}\n\n\t// From 54 to 53 Bits.\n\tretMantissa += retMantissa \u0026 1\n\tretMantissa \u003e\u003e= 1\n\tif retMantissa\u003e\u003e53 \u003e 0 {\n\t\tretMantissa \u003e\u003e= 1\n\t\tretExp2 += 1\n\t}\n\n\t// retExp2 is a uint64. Zero or underflow means that we're in subnormal\n\t// float64 space. 0x7FF or above means that we're in Inf/NaN float64 space.\n\t//\n\t// The if block is equivalent to (but has fewer branches than):\n\t// if retExp2 \u003c= 0 || retExp2 \u003e= 0x7FF { etc }\n\tif retExp2-1 \u003e= 0x7FF-1 {\n\t\treturn 0, false\n\t}\n\n\tretBits := retExp2\u003c\u003c52 | retMantissa\u00260x000FFFFF_FFFFFFFF\n\tif neg {\n\t\tretBits |= 0x80000000_00000000\n\t}\n\n\treturn math.Float64frombits(retBits), true\n}\n\n// detailedPowersOfTen{Min,Max}Exp10 is the power of 10 represented by the\n// first and last rows of detailedPowersOfTen. Both bounds are inclusive.\nconst (\n\tdetailedPowersOfTenMinExp10 = -348\n\tdetailedPowersOfTenMaxExp10 = +347\n)\n\n// detailedPowersOfTen contains 128-bit mantissa approximations (rounded down)\n// to the powers of 10. For example:\n//\n// - 1e43 ≈ (0xE596B7B0_C643C719 * (2 ** 79))\n// - 1e43 = (0xE596B7B0_C643C719_6D9CCD05_D0000000 * (2 ** 15))\n//\n// The mantissas are explicitly listed. The exponents are implied by a linear\n// expression with slope 217706.0/65536.0 ≈ log(10)/log(2).\n//\n// The table was generated by\n// https://github.com/google/wuffs/blob/ba3818cb6b473a2ed0b38ecfc07dbbd3a97e8ae7/script/print-mpb-powers-of-10.go\nvar detailedPowersOfTen = [...][2]uint64{\n\t{0x1732C869CD60E453, 0xFA8FD5A0081C0288}, // 1e-348\n\t{0x0E7FBD42205C8EB4, 0x9C99E58405118195}, // 1e-347\n\t{0x521FAC92A873B261, 0xC3C05EE50655E1FA}, // 1e-346\n\t{0xE6A797B752909EF9, 0xF4B0769E47EB5A78}, // 1e-345\n\t{0x9028BED2939A635C, 0x98EE4A22ECF3188B}, // 1e-344\n\t{0x7432EE873880FC33, 0xBF29DCABA82FDEAE}, // 1e-343\n\t{0x113FAA2906A13B3F, 0xEEF453D6923BD65A}, // 1e-342\n\t{0x4AC7CA59A424C507, 0x9558B4661B6565F8}, // 1e-341\n\t{0x5D79BCF00D2DF649, 0xBAAEE17FA23EBF76}, // 1e-340\n\t{0xF4D82C2C107973DC, 0xE95A99DF8ACE6F53}, // 1e-339\n\t{0x79071B9B8A4BE869, 0x91D8A02BB6C10594}, // 1e-338\n\t{0x9748E2826CDEE284, 0xB64EC836A47146F9}, // 1e-337\n\t{0xFD1B1B2308169B25, 0xE3E27A444D8D98B7}, // 1e-336\n\t{0xFE30F0F5E50E20F7, 0x8E6D8C6AB0787F72}, // 1e-335\n\t{0xBDBD2D335E51A935, 0xB208EF855C969F4F}, // 1e-334\n\t{0xAD2C788035E61382, 0xDE8B2B66B3BC4723}, // 1e-333\n\t{0x4C3BCB5021AFCC31, 0x8B16FB203055AC76}, // 1e-332\n\t{0xDF4ABE242A1BBF3D, 0xADDCB9E83C6B1793}, // 1e-331\n\t{0xD71D6DAD34A2AF0D, 0xD953E8624B85DD78}, // 1e-330\n\t{0x8672648C40E5AD68, 0x87D4713D6F33AA6B}, // 1e-329\n\t{0x680EFDAF511F18C2, 0xA9C98D8CCB009506}, // 1e-328\n\t{0x0212BD1B2566DEF2, 0xD43BF0EFFDC0BA48}, // 1e-327\n\t{0x014BB630F7604B57, 0x84A57695FE98746D}, // 1e-326\n\t{0x419EA3BD35385E2D, 0xA5CED43B7E3E9188}, // 1e-325\n\t{0x52064CAC828675B9, 0xCF42894A5DCE35EA}, // 1e-324\n\t{0x7343EFEBD1940993, 0x818995CE7AA0E1B2}, // 1e-323\n\t{0x1014EBE6C5F90BF8, 0xA1EBFB4219491A1F}, // 1e-322\n\t{0xD41A26E077774EF6, 0xCA66FA129F9B60A6}, // 1e-321\n\t{0x8920B098955522B4, 0xFD00B897478238D0}, // 1e-320\n\t{0x55B46E5F5D5535B0, 0x9E20735E8CB16382}, // 1e-319\n\t{0xEB2189F734AA831D, 0xC5A890362FDDBC62}, // 1e-318\n\t{0xA5E9EC7501D523E4, 0xF712B443BBD52B7B}, // 1e-317\n\t{0x47B233C92125366E, 0x9A6BB0AA55653B2D}, // 1e-316\n\t{0x999EC0BB696E840A, 0xC1069CD4EABE89F8}, // 1e-315\n\t{0xC00670EA43CA250D, 0xF148440A256E2C76}, // 1e-314\n\t{0x380406926A5E5728, 0x96CD2A865764DBCA}, // 1e-313\n\t{0xC605083704F5ECF2, 0xBC807527ED3E12BC}, // 1e-312\n\t{0xF7864A44C633682E, 0xEBA09271E88D976B}, // 1e-311\n\t{0x7AB3EE6AFBE0211D, 0x93445B8731587EA3}, // 1e-310\n\t{0x5960EA05BAD82964, 0xB8157268FDAE9E4C}, // 1e-309\n\t{0x6FB92487298E33BD, 0xE61ACF033D1A45DF}, // 1e-308\n\t{0xA5D3B6D479F8E056, 0x8FD0C16206306BAB}, // 1e-307\n\t{0x8F48A4899877186C, 0xB3C4F1BA87BC8696}, // 1e-306\n\t{0x331ACDABFE94DE87, 0xE0B62E2929ABA83C}, // 1e-305\n\t{0x9FF0C08B7F1D0B14, 0x8C71DCD9BA0B4925}, // 1e-304\n\t{0x07ECF0AE5EE44DD9, 0xAF8E5410288E1B6F}, // 1e-303\n\t{0xC9E82CD9F69D6150, 0xDB71E91432B1A24A}, // 1e-302\n\t{0xBE311C083A225CD2, 0x892731AC9FAF056E}, // 1e-301\n\t{0x6DBD630A48AAF406, 0xAB70FE17C79AC6CA}, // 1e-300\n\t{0x092CBBCCDAD5B108, 0xD64D3D9DB981787D}, // 1e-299\n\t{0x25BBF56008C58EA5, 0x85F0468293F0EB4E}, // 1e-298\n\t{0xAF2AF2B80AF6F24E, 0xA76C582338ED2621}, // 1e-297\n\t{0x1AF5AF660DB4AEE1, 0xD1476E2C07286FAA}, // 1e-296\n\t{0x50D98D9FC890ED4D, 0x82CCA4DB847945CA}, // 1e-295\n\t{0xE50FF107BAB528A0, 0xA37FCE126597973C}, // 1e-294\n\t{0x1E53ED49A96272C8, 0xCC5FC196FEFD7D0C}, // 1e-293\n\t{0x25E8E89C13BB0F7A, 0xFF77B1FCBEBCDC4F}, // 1e-292\n\t{0x77B191618C54E9AC, 0x9FAACF3DF73609B1}, // 1e-291\n\t{0xD59DF5B9EF6A2417, 0xC795830D75038C1D}, // 1e-290\n\t{0x4B0573286B44AD1D, 0xF97AE3D0D2446F25}, // 1e-289\n\t{0x4EE367F9430AEC32, 0x9BECCE62836AC577}, // 1e-288\n\t{0x229C41F793CDA73F, 0xC2E801FB244576D5}, // 1e-287\n\t{0x6B43527578C1110F, 0xF3A20279ED56D48A}, // 1e-286\n\t{0x830A13896B78AAA9, 0x9845418C345644D6}, // 1e-285\n\t{0x23CC986BC656D553, 0xBE5691EF416BD60C}, // 1e-284\n\t{0x2CBFBE86B7EC8AA8, 0xEDEC366B11C6CB8F}, // 1e-283\n\t{0x7BF7D71432F3D6A9, 0x94B3A202EB1C3F39}, // 1e-282\n\t{0xDAF5CCD93FB0CC53, 0xB9E08A83A5E34F07}, // 1e-281\n\t{0xD1B3400F8F9CFF68, 0xE858AD248F5C22C9}, // 1e-280\n\t{0x23100809B9C21FA1, 0x91376C36D99995BE}, // 1e-279\n\t{0xABD40A0C2832A78A, 0xB58547448FFFFB2D}, // 1e-278\n\t{0x16C90C8F323F516C, 0xE2E69915B3FFF9F9}, // 1e-277\n\t{0xAE3DA7D97F6792E3, 0x8DD01FAD907FFC3B}, // 1e-276\n\t{0x99CD11CFDF41779C, 0xB1442798F49FFB4A}, // 1e-275\n\t{0x40405643D711D583, 0xDD95317F31C7FA1D}, // 1e-274\n\t{0x482835EA666B2572, 0x8A7D3EEF7F1CFC52}, // 1e-273\n\t{0xDA3243650005EECF, 0xAD1C8EAB5EE43B66}, // 1e-272\n\t{0x90BED43E40076A82, 0xD863B256369D4A40}, // 1e-271\n\t{0x5A7744A6E804A291, 0x873E4F75E2224E68}, // 1e-270\n\t{0x711515D0A205CB36, 0xA90DE3535AAAE202}, // 1e-269\n\t{0x0D5A5B44CA873E03, 0xD3515C2831559A83}, // 1e-268\n\t{0xE858790AFE9486C2, 0x8412D9991ED58091}, // 1e-267\n\t{0x626E974DBE39A872, 0xA5178FFF668AE0B6}, // 1e-266\n\t{0xFB0A3D212DC8128F, 0xCE5D73FF402D98E3}, // 1e-265\n\t{0x7CE66634BC9D0B99, 0x80FA687F881C7F8E}, // 1e-264\n\t{0x1C1FFFC1EBC44E80, 0xA139029F6A239F72}, // 1e-263\n\t{0xA327FFB266B56220, 0xC987434744AC874E}, // 1e-262\n\t{0x4BF1FF9F0062BAA8, 0xFBE9141915D7A922}, // 1e-261\n\t{0x6F773FC3603DB4A9, 0x9D71AC8FADA6C9B5}, // 1e-260\n\t{0xCB550FB4384D21D3, 0xC4CE17B399107C22}, // 1e-259\n\t{0x7E2A53A146606A48, 0xF6019DA07F549B2B}, // 1e-258\n\t{0x2EDA7444CBFC426D, 0x99C102844F94E0FB}, // 1e-257\n\t{0xFA911155FEFB5308, 0xC0314325637A1939}, // 1e-256\n\t{0x793555AB7EBA27CA, 0xF03D93EEBC589F88}, // 1e-255\n\t{0x4BC1558B2F3458DE, 0x96267C7535B763B5}, // 1e-254\n\t{0x9EB1AAEDFB016F16, 0xBBB01B9283253CA2}, // 1e-253\n\t{0x465E15A979C1CADC, 0xEA9C227723EE8BCB}, // 1e-252\n\t{0x0BFACD89EC191EC9, 0x92A1958A7675175F}, // 1e-251\n\t{0xCEF980EC671F667B, 0xB749FAED14125D36}, // 1e-250\n\t{0x82B7E12780E7401A, 0xE51C79A85916F484}, // 1e-249\n\t{0xD1B2ECB8B0908810, 0x8F31CC0937AE58D2}, // 1e-248\n\t{0x861FA7E6DCB4AA15, 0xB2FE3F0B8599EF07}, // 1e-247\n\t{0x67A791E093E1D49A, 0xDFBDCECE67006AC9}, // 1e-246\n\t{0xE0C8BB2C5C6D24E0, 0x8BD6A141006042BD}, // 1e-245\n\t{0x58FAE9F773886E18, 0xAECC49914078536D}, // 1e-244\n\t{0xAF39A475506A899E, 0xDA7F5BF590966848}, // 1e-243\n\t{0x6D8406C952429603, 0x888F99797A5E012D}, // 1e-242\n\t{0xC8E5087BA6D33B83, 0xAAB37FD7D8F58178}, // 1e-241\n\t{0xFB1E4A9A90880A64, 0xD5605FCDCF32E1D6}, // 1e-240\n\t{0x5CF2EEA09A55067F, 0x855C3BE0A17FCD26}, // 1e-239\n\t{0xF42FAA48C0EA481E, 0xA6B34AD8C9DFC06F}, // 1e-238\n\t{0xF13B94DAF124DA26, 0xD0601D8EFC57B08B}, // 1e-237\n\t{0x76C53D08D6B70858, 0x823C12795DB6CE57}, // 1e-236\n\t{0x54768C4B0C64CA6E, 0xA2CB1717B52481ED}, // 1e-235\n\t{0xA9942F5DCF7DFD09, 0xCB7DDCDDA26DA268}, // 1e-234\n\t{0xD3F93B35435D7C4C, 0xFE5D54150B090B02}, // 1e-233\n\t{0xC47BC5014A1A6DAF, 0x9EFA548D26E5A6E1}, // 1e-232\n\t{0x359AB6419CA1091B, 0xC6B8E9B0709F109A}, // 1e-231\n\t{0xC30163D203C94B62, 0xF867241C8CC6D4C0}, // 1e-230\n\t{0x79E0DE63425DCF1D, 0x9B407691D7FC44F8}, // 1e-229\n\t{0x985915FC12F542E4, 0xC21094364DFB5636}, // 1e-228\n\t{0x3E6F5B7B17B2939D, 0xF294B943E17A2BC4}, // 1e-227\n\t{0xA705992CEECF9C42, 0x979CF3CA6CEC5B5A}, // 1e-226\n\t{0x50C6FF782A838353, 0xBD8430BD08277231}, // 1e-225\n\t{0xA4F8BF5635246428, 0xECE53CEC4A314EBD}, // 1e-224\n\t{0x871B7795E136BE99, 0x940F4613AE5ED136}, // 1e-223\n\t{0x28E2557B59846E3F, 0xB913179899F68584}, // 1e-222\n\t{0x331AEADA2FE589CF, 0xE757DD7EC07426E5}, // 1e-221\n\t{0x3FF0D2C85DEF7621, 0x9096EA6F3848984F}, // 1e-220\n\t{0x0FED077A756B53A9, 0xB4BCA50B065ABE63}, // 1e-219\n\t{0xD3E8495912C62894, 0xE1EBCE4DC7F16DFB}, // 1e-218\n\t{0x64712DD7ABBBD95C, 0x8D3360F09CF6E4BD}, // 1e-217\n\t{0xBD8D794D96AACFB3, 0xB080392CC4349DEC}, // 1e-216\n\t{0xECF0D7A0FC5583A0, 0xDCA04777F541C567}, // 1e-215\n\t{0xF41686C49DB57244, 0x89E42CAAF9491B60}, // 1e-214\n\t{0x311C2875C522CED5, 0xAC5D37D5B79B6239}, // 1e-213\n\t{0x7D633293366B828B, 0xD77485CB25823AC7}, // 1e-212\n\t{0xAE5DFF9C02033197, 0x86A8D39EF77164BC}, // 1e-211\n\t{0xD9F57F830283FDFC, 0xA8530886B54DBDEB}, // 1e-210\n\t{0xD072DF63C324FD7B, 0xD267CAA862A12D66}, // 1e-209\n\t{0x4247CB9E59F71E6D, 0x8380DEA93DA4BC60}, // 1e-208\n\t{0x52D9BE85F074E608, 0xA46116538D0DEB78}, // 1e-207\n\t{0x67902E276C921F8B, 0xCD795BE870516656}, // 1e-206\n\t{0x00BA1CD8A3DB53B6, 0x806BD9714632DFF6}, // 1e-205\n\t{0x80E8A40ECCD228A4, 0xA086CFCD97BF97F3}, // 1e-204\n\t{0x6122CD128006B2CD, 0xC8A883C0FDAF7DF0}, // 1e-203\n\t{0x796B805720085F81, 0xFAD2A4B13D1B5D6C}, // 1e-202\n\t{0xCBE3303674053BB0, 0x9CC3A6EEC6311A63}, // 1e-201\n\t{0xBEDBFC4411068A9C, 0xC3F490AA77BD60FC}, // 1e-200\n\t{0xEE92FB5515482D44, 0xF4F1B4D515ACB93B}, // 1e-199\n\t{0x751BDD152D4D1C4A, 0x991711052D8BF3C5}, // 1e-198\n\t{0xD262D45A78A0635D, 0xBF5CD54678EEF0B6}, // 1e-197\n\t{0x86FB897116C87C34, 0xEF340A98172AACE4}, // 1e-196\n\t{0xD45D35E6AE3D4DA0, 0x9580869F0E7AAC0E}, // 1e-195\n\t{0x8974836059CCA109, 0xBAE0A846D2195712}, // 1e-194\n\t{0x2BD1A438703FC94B, 0xE998D258869FACD7}, // 1e-193\n\t{0x7B6306A34627DDCF, 0x91FF83775423CC06}, // 1e-192\n\t{0x1A3BC84C17B1D542, 0xB67F6455292CBF08}, // 1e-191\n\t{0x20CABA5F1D9E4A93, 0xE41F3D6A7377EECA}, // 1e-190\n\t{0x547EB47B7282EE9C, 0x8E938662882AF53E}, // 1e-189\n\t{0xE99E619A4F23AA43, 0xB23867FB2A35B28D}, // 1e-188\n\t{0x6405FA00E2EC94D4, 0xDEC681F9F4C31F31}, // 1e-187\n\t{0xDE83BC408DD3DD04, 0x8B3C113C38F9F37E}, // 1e-186\n\t{0x9624AB50B148D445, 0xAE0B158B4738705E}, // 1e-185\n\t{0x3BADD624DD9B0957, 0xD98DDAEE19068C76}, // 1e-184\n\t{0xE54CA5D70A80E5D6, 0x87F8A8D4CFA417C9}, // 1e-183\n\t{0x5E9FCF4CCD211F4C, 0xA9F6D30A038D1DBC}, // 1e-182\n\t{0x7647C3200069671F, 0xD47487CC8470652B}, // 1e-181\n\t{0x29ECD9F40041E073, 0x84C8D4DFD2C63F3B}, // 1e-180\n\t{0xF468107100525890, 0xA5FB0A17C777CF09}, // 1e-179\n\t{0x7182148D4066EEB4, 0xCF79CC9DB955C2CC}, // 1e-178\n\t{0xC6F14CD848405530, 0x81AC1FE293D599BF}, // 1e-177\n\t{0xB8ADA00E5A506A7C, 0xA21727DB38CB002F}, // 1e-176\n\t{0xA6D90811F0E4851C, 0xCA9CF1D206FDC03B}, // 1e-175\n\t{0x908F4A166D1DA663, 0xFD442E4688BD304A}, // 1e-174\n\t{0x9A598E4E043287FE, 0x9E4A9CEC15763E2E}, // 1e-173\n\t{0x40EFF1E1853F29FD, 0xC5DD44271AD3CDBA}, // 1e-172\n\t{0xD12BEE59E68EF47C, 0xF7549530E188C128}, // 1e-171\n\t{0x82BB74F8301958CE, 0x9A94DD3E8CF578B9}, // 1e-170\n\t{0xE36A52363C1FAF01, 0xC13A148E3032D6E7}, // 1e-169\n\t{0xDC44E6C3CB279AC1, 0xF18899B1BC3F8CA1}, // 1e-168\n\t{0x29AB103A5EF8C0B9, 0x96F5600F15A7B7E5}, // 1e-167\n\t{0x7415D448F6B6F0E7, 0xBCB2B812DB11A5DE}, // 1e-166\n\t{0x111B495B3464AD21, 0xEBDF661791D60F56}, // 1e-165\n\t{0xCAB10DD900BEEC34, 0x936B9FCEBB25C995}, // 1e-164\n\t{0x3D5D514F40EEA742, 0xB84687C269EF3BFB}, // 1e-163\n\t{0x0CB4A5A3112A5112, 0xE65829B3046B0AFA}, // 1e-162\n\t{0x47F0E785EABA72AB, 0x8FF71A0FE2C2E6DC}, // 1e-161\n\t{0x59ED216765690F56, 0xB3F4E093DB73A093}, // 1e-160\n\t{0x306869C13EC3532C, 0xE0F218B8D25088B8}, // 1e-159\n\t{0x1E414218C73A13FB, 0x8C974F7383725573}, // 1e-158\n\t{0xE5D1929EF90898FA, 0xAFBD2350644EEACF}, // 1e-157\n\t{0xDF45F746B74ABF39, 0xDBAC6C247D62A583}, // 1e-156\n\t{0x6B8BBA8C328EB783, 0x894BC396CE5DA772}, // 1e-155\n\t{0x066EA92F3F326564, 0xAB9EB47C81F5114F}, // 1e-154\n\t{0xC80A537B0EFEFEBD, 0xD686619BA27255A2}, // 1e-153\n\t{0xBD06742CE95F5F36, 0x8613FD0145877585}, // 1e-152\n\t{0x2C48113823B73704, 0xA798FC4196E952E7}, // 1e-151\n\t{0xF75A15862CA504C5, 0xD17F3B51FCA3A7A0}, // 1e-150\n\t{0x9A984D73DBE722FB, 0x82EF85133DE648C4}, // 1e-149\n\t{0xC13E60D0D2E0EBBA, 0xA3AB66580D5FDAF5}, // 1e-148\n\t{0x318DF905079926A8, 0xCC963FEE10B7D1B3}, // 1e-147\n\t{0xFDF17746497F7052, 0xFFBBCFE994E5C61F}, // 1e-146\n\t{0xFEB6EA8BEDEFA633, 0x9FD561F1FD0F9BD3}, // 1e-145\n\t{0xFE64A52EE96B8FC0, 0xC7CABA6E7C5382C8}, // 1e-144\n\t{0x3DFDCE7AA3C673B0, 0xF9BD690A1B68637B}, // 1e-143\n\t{0x06BEA10CA65C084E, 0x9C1661A651213E2D}, // 1e-142\n\t{0x486E494FCFF30A62, 0xC31BFA0FE5698DB8}, // 1e-141\n\t{0x5A89DBA3C3EFCCFA, 0xF3E2F893DEC3F126}, // 1e-140\n\t{0xF89629465A75E01C, 0x986DDB5C6B3A76B7}, // 1e-139\n\t{0xF6BBB397F1135823, 0xBE89523386091465}, // 1e-138\n\t{0x746AA07DED582E2C, 0xEE2BA6C0678B597F}, // 1e-137\n\t{0xA8C2A44EB4571CDC, 0x94DB483840B717EF}, // 1e-136\n\t{0x92F34D62616CE413, 0xBA121A4650E4DDEB}, // 1e-135\n\t{0x77B020BAF9C81D17, 0xE896A0D7E51E1566}, // 1e-134\n\t{0x0ACE1474DC1D122E, 0x915E2486EF32CD60}, // 1e-133\n\t{0x0D819992132456BA, 0xB5B5ADA8AAFF80B8}, // 1e-132\n\t{0x10E1FFF697ED6C69, 0xE3231912D5BF60E6}, // 1e-131\n\t{0xCA8D3FFA1EF463C1, 0x8DF5EFABC5979C8F}, // 1e-130\n\t{0xBD308FF8A6B17CB2, 0xB1736B96B6FD83B3}, // 1e-129\n\t{0xAC7CB3F6D05DDBDE, 0xDDD0467C64BCE4A0}, // 1e-128\n\t{0x6BCDF07A423AA96B, 0x8AA22C0DBEF60EE4}, // 1e-127\n\t{0x86C16C98D2C953C6, 0xAD4AB7112EB3929D}, // 1e-126\n\t{0xE871C7BF077BA8B7, 0xD89D64D57A607744}, // 1e-125\n\t{0x11471CD764AD4972, 0x87625F056C7C4A8B}, // 1e-124\n\t{0xD598E40D3DD89BCF, 0xA93AF6C6C79B5D2D}, // 1e-123\n\t{0x4AFF1D108D4EC2C3, 0xD389B47879823479}, // 1e-122\n\t{0xCEDF722A585139BA, 0x843610CB4BF160CB}, // 1e-121\n\t{0xC2974EB4EE658828, 0xA54394FE1EEDB8FE}, // 1e-120\n\t{0x733D226229FEEA32, 0xCE947A3DA6A9273E}, // 1e-119\n\t{0x0806357D5A3F525F, 0x811CCC668829B887}, // 1e-118\n\t{0xCA07C2DCB0CF26F7, 0xA163FF802A3426A8}, // 1e-117\n\t{0xFC89B393DD02F0B5, 0xC9BCFF6034C13052}, // 1e-116\n\t{0xBBAC2078D443ACE2, 0xFC2C3F3841F17C67}, // 1e-115\n\t{0xD54B944B84AA4C0D, 0x9D9BA7832936EDC0}, // 1e-114\n\t{0x0A9E795E65D4DF11, 0xC5029163F384A931}, // 1e-113\n\t{0x4D4617B5FF4A16D5, 0xF64335BCF065D37D}, // 1e-112\n\t{0x504BCED1BF8E4E45, 0x99EA0196163FA42E}, // 1e-111\n\t{0xE45EC2862F71E1D6, 0xC06481FB9BCF8D39}, // 1e-110\n\t{0x5D767327BB4E5A4C, 0xF07DA27A82C37088}, // 1e-109\n\t{0x3A6A07F8D510F86F, 0x964E858C91BA2655}, // 1e-108\n\t{0x890489F70A55368B, 0xBBE226EFB628AFEA}, // 1e-107\n\t{0x2B45AC74CCEA842E, 0xEADAB0ABA3B2DBE5}, // 1e-106\n\t{0x3B0B8BC90012929D, 0x92C8AE6B464FC96F}, // 1e-105\n\t{0x09CE6EBB40173744, 0xB77ADA0617E3BBCB}, // 1e-104\n\t{0xCC420A6A101D0515, 0xE55990879DDCAABD}, // 1e-103\n\t{0x9FA946824A12232D, 0x8F57FA54C2A9EAB6}, // 1e-102\n\t{0x47939822DC96ABF9, 0xB32DF8E9F3546564}, // 1e-101\n\t{0x59787E2B93BC56F7, 0xDFF9772470297EBD}, // 1e-100\n\t{0x57EB4EDB3C55B65A, 0x8BFBEA76C619EF36}, // 1e-99\n\t{0xEDE622920B6B23F1, 0xAEFAE51477A06B03}, // 1e-98\n\t{0xE95FAB368E45ECED, 0xDAB99E59958885C4}, // 1e-97\n\t{0x11DBCB0218EBB414, 0x88B402F7FD75539B}, // 1e-96\n\t{0xD652BDC29F26A119, 0xAAE103B5FCD2A881}, // 1e-95\n\t{0x4BE76D3346F0495F, 0xD59944A37C0752A2}, // 1e-94\n\t{0x6F70A4400C562DDB, 0x857FCAE62D8493A5}, // 1e-93\n\t{0xCB4CCD500F6BB952, 0xA6DFBD9FB8E5B88E}, // 1e-92\n\t{0x7E2000A41346A7A7, 0xD097AD07A71F26B2}, // 1e-91\n\t{0x8ED400668C0C28C8, 0x825ECC24C873782F}, // 1e-90\n\t{0x728900802F0F32FA, 0xA2F67F2DFA90563B}, // 1e-89\n\t{0x4F2B40A03AD2FFB9, 0xCBB41EF979346BCA}, // 1e-88\n\t{0xE2F610C84987BFA8, 0xFEA126B7D78186BC}, // 1e-87\n\t{0x0DD9CA7D2DF4D7C9, 0x9F24B832E6B0F436}, // 1e-86\n\t{0x91503D1C79720DBB, 0xC6EDE63FA05D3143}, // 1e-85\n\t{0x75A44C6397CE912A, 0xF8A95FCF88747D94}, // 1e-84\n\t{0xC986AFBE3EE11ABA, 0x9B69DBE1B548CE7C}, // 1e-83\n\t{0xFBE85BADCE996168, 0xC24452DA229B021B}, // 1e-82\n\t{0xFAE27299423FB9C3, 0xF2D56790AB41C2A2}, // 1e-81\n\t{0xDCCD879FC967D41A, 0x97C560BA6B0919A5}, // 1e-80\n\t{0x5400E987BBC1C920, 0xBDB6B8E905CB600F}, // 1e-79\n\t{0x290123E9AAB23B68, 0xED246723473E3813}, // 1e-78\n\t{0xF9A0B6720AAF6521, 0x9436C0760C86E30B}, // 1e-77\n\t{0xF808E40E8D5B3E69, 0xB94470938FA89BCE}, // 1e-76\n\t{0xB60B1D1230B20E04, 0xE7958CB87392C2C2}, // 1e-75\n\t{0xB1C6F22B5E6F48C2, 0x90BD77F3483BB9B9}, // 1e-74\n\t{0x1E38AEB6360B1AF3, 0xB4ECD5F01A4AA828}, // 1e-73\n\t{0x25C6DA63C38DE1B0, 0xE2280B6C20DD5232}, // 1e-72\n\t{0x579C487E5A38AD0E, 0x8D590723948A535F}, // 1e-71\n\t{0x2D835A9DF0C6D851, 0xB0AF48EC79ACE837}, // 1e-70\n\t{0xF8E431456CF88E65, 0xDCDB1B2798182244}, // 1e-69\n\t{0x1B8E9ECB641B58FF, 0x8A08F0F8BF0F156B}, // 1e-68\n\t{0xE272467E3D222F3F, 0xAC8B2D36EED2DAC5}, // 1e-67\n\t{0x5B0ED81DCC6ABB0F, 0xD7ADF884AA879177}, // 1e-66\n\t{0x98E947129FC2B4E9, 0x86CCBB52EA94BAEA}, // 1e-65\n\t{0x3F2398D747B36224, 0xA87FEA27A539E9A5}, // 1e-64\n\t{0x8EEC7F0D19A03AAD, 0xD29FE4B18E88640E}, // 1e-63\n\t{0x1953CF68300424AC, 0x83A3EEEEF9153E89}, // 1e-62\n\t{0x5FA8C3423C052DD7, 0xA48CEAAAB75A8E2B}, // 1e-61\n\t{0x3792F412CB06794D, 0xCDB02555653131B6}, // 1e-60\n\t{0xE2BBD88BBEE40BD0, 0x808E17555F3EBF11}, // 1e-59\n\t{0x5B6ACEAEAE9D0EC4, 0xA0B19D2AB70E6ED6}, // 1e-58\n\t{0xF245825A5A445275, 0xC8DE047564D20A8B}, // 1e-57\n\t{0xEED6E2F0F0D56712, 0xFB158592BE068D2E}, // 1e-56\n\t{0x55464DD69685606B, 0x9CED737BB6C4183D}, // 1e-55\n\t{0xAA97E14C3C26B886, 0xC428D05AA4751E4C}, // 1e-54\n\t{0xD53DD99F4B3066A8, 0xF53304714D9265DF}, // 1e-53\n\t{0xE546A8038EFE4029, 0x993FE2C6D07B7FAB}, // 1e-52\n\t{0xDE98520472BDD033, 0xBF8FDB78849A5F96}, // 1e-51\n\t{0x963E66858F6D4440, 0xEF73D256A5C0F77C}, // 1e-50\n\t{0xDDE7001379A44AA8, 0x95A8637627989AAD}, // 1e-49\n\t{0x5560C018580D5D52, 0xBB127C53B17EC159}, // 1e-48\n\t{0xAAB8F01E6E10B4A6, 0xE9D71B689DDE71AF}, // 1e-47\n\t{0xCAB3961304CA70E8, 0x9226712162AB070D}, // 1e-46\n\t{0x3D607B97C5FD0D22, 0xB6B00D69BB55C8D1}, // 1e-45\n\t{0x8CB89A7DB77C506A, 0xE45C10C42A2B3B05}, // 1e-44\n\t{0x77F3608E92ADB242, 0x8EB98A7A9A5B04E3}, // 1e-43\n\t{0x55F038B237591ED3, 0xB267ED1940F1C61C}, // 1e-42\n\t{0x6B6C46DEC52F6688, 0xDF01E85F912E37A3}, // 1e-41\n\t{0x2323AC4B3B3DA015, 0x8B61313BBABCE2C6}, // 1e-40\n\t{0xABEC975E0A0D081A, 0xAE397D8AA96C1B77}, // 1e-39\n\t{0x96E7BD358C904A21, 0xD9C7DCED53C72255}, // 1e-38\n\t{0x7E50D64177DA2E54, 0x881CEA14545C7575}, // 1e-37\n\t{0xDDE50BD1D5D0B9E9, 0xAA242499697392D2}, // 1e-36\n\t{0x955E4EC64B44E864, 0xD4AD2DBFC3D07787}, // 1e-35\n\t{0xBD5AF13BEF0B113E, 0x84EC3C97DA624AB4}, // 1e-34\n\t{0xECB1AD8AEACDD58E, 0xA6274BBDD0FADD61}, // 1e-33\n\t{0x67DE18EDA5814AF2, 0xCFB11EAD453994BA}, // 1e-32\n\t{0x80EACF948770CED7, 0x81CEB32C4B43FCF4}, // 1e-31\n\t{0xA1258379A94D028D, 0xA2425FF75E14FC31}, // 1e-30\n\t{0x096EE45813A04330, 0xCAD2F7F5359A3B3E}, // 1e-29\n\t{0x8BCA9D6E188853FC, 0xFD87B5F28300CA0D}, // 1e-28\n\t{0x775EA264CF55347D, 0x9E74D1B791E07E48}, // 1e-27\n\t{0x95364AFE032A819D, 0xC612062576589DDA}, // 1e-26\n\t{0x3A83DDBD83F52204, 0xF79687AED3EEC551}, // 1e-25\n\t{0xC4926A9672793542, 0x9ABE14CD44753B52}, // 1e-24\n\t{0x75B7053C0F178293, 0xC16D9A0095928A27}, // 1e-23\n\t{0x5324C68B12DD6338, 0xF1C90080BAF72CB1}, // 1e-22\n\t{0xD3F6FC16EBCA5E03, 0x971DA05074DA7BEE}, // 1e-21\n\t{0x88F4BB1CA6BCF584, 0xBCE5086492111AEA}, // 1e-20\n\t{0x2B31E9E3D06C32E5, 0xEC1E4A7DB69561A5}, // 1e-19\n\t{0x3AFF322E62439FCF, 0x9392EE8E921D5D07}, // 1e-18\n\t{0x09BEFEB9FAD487C2, 0xB877AA3236A4B449}, // 1e-17\n\t{0x4C2EBE687989A9B3, 0xE69594BEC44DE15B}, // 1e-16\n\t{0x0F9D37014BF60A10, 0x901D7CF73AB0ACD9}, // 1e-15\n\t{0x538484C19EF38C94, 0xB424DC35095CD80F}, // 1e-14\n\t{0x2865A5F206B06FB9, 0xE12E13424BB40E13}, // 1e-13\n\t{0xF93F87B7442E45D3, 0x8CBCCC096F5088CB}, // 1e-12\n\t{0xF78F69A51539D748, 0xAFEBFF0BCB24AAFE}, // 1e-11\n\t{0xB573440E5A884D1B, 0xDBE6FECEBDEDD5BE}, // 1e-10\n\t{0x31680A88F8953030, 0x89705F4136B4A597}, // 1e-9\n\t{0xFDC20D2B36BA7C3D, 0xABCC77118461CEFC}, // 1e-8\n\t{0x3D32907604691B4C, 0xD6BF94D5E57A42BC}, // 1e-7\n\t{0xA63F9A49C2C1B10F, 0x8637BD05AF6C69B5}, // 1e-6\n\t{0x0FCF80DC33721D53, 0xA7C5AC471B478423}, // 1e-5\n\t{0xD3C36113404EA4A8, 0xD1B71758E219652B}, // 1e-4\n\t{0x645A1CAC083126E9, 0x83126E978D4FDF3B}, // 1e-3\n\t{0x3D70A3D70A3D70A3, 0xA3D70A3D70A3D70A}, // 1e-2\n\t{0xCCCCCCCCCCCCCCCC, 0xCCCCCCCCCCCCCCCC}, // 1e-1\n\t{0x0000000000000000, 0x8000000000000000}, // 1e0\n\t{0x0000000000000000, 0xA000000000000000}, // 1e1\n\t{0x0000000000000000, 0xC800000000000000}, // 1e2\n\t{0x0000000000000000, 0xFA00000000000000}, // 1e3\n\t{0x0000000000000000, 0x9C40000000000000}, // 1e4\n\t{0x0000000000000000, 0xC350000000000000}, // 1e5\n\t{0x0000000000000000, 0xF424000000000000}, // 1e6\n\t{0x0000000000000000, 0x9896800000000000}, // 1e7\n\t{0x0000000000000000, 0xBEBC200000000000}, // 1e8\n\t{0x0000000000000000, 0xEE6B280000000000}, // 1e9\n\t{0x0000000000000000, 0x9502F90000000000}, // 1e10\n\t{0x0000000000000000, 0xBA43B74000000000}, // 1e11\n\t{0x0000000000000000, 0xE8D4A51000000000}, // 1e12\n\t{0x0000000000000000, 0x9184E72A00000000}, // 1e13\n\t{0x0000000000000000, 0xB5E620F480000000}, // 1e14\n\t{0x0000000000000000, 0xE35FA931A0000000}, // 1e15\n\t{0x0000000000000000, 0x8E1BC9BF04000000}, // 1e16\n\t{0x0000000000000000, 0xB1A2BC2EC5000000}, // 1e17\n\t{0x0000000000000000, 0xDE0B6B3A76400000}, // 1e18\n\t{0x0000000000000000, 0x8AC7230489E80000}, // 1e19\n\t{0x0000000000000000, 0xAD78EBC5AC620000}, // 1e20\n\t{0x0000000000000000, 0xD8D726B7177A8000}, // 1e21\n\t{0x0000000000000000, 0x878678326EAC9000}, // 1e22\n\t{0x0000000000000000, 0xA968163F0A57B400}, // 1e23\n\t{0x0000000000000000, 0xD3C21BCECCEDA100}, // 1e24\n\t{0x0000000000000000, 0x84595161401484A0}, // 1e25\n\t{0x0000000000000000, 0xA56FA5B99019A5C8}, // 1e26\n\t{0x0000000000000000, 0xCECB8F27F4200F3A}, // 1e27\n\t{0x4000000000000000, 0x813F3978F8940984}, // 1e28\n\t{0x5000000000000000, 0xA18F07D736B90BE5}, // 1e29\n\t{0xA400000000000000, 0xC9F2C9CD04674EDE}, // 1e30\n\t{0x4D00000000000000, 0xFC6F7C4045812296}, // 1e31\n\t{0xF020000000000000, 0x9DC5ADA82B70B59D}, // 1e32\n\t{0x6C28000000000000, 0xC5371912364CE305}, // 1e33\n\t{0xC732000000000000, 0xF684DF56C3E01BC6}, // 1e34\n\t{0x3C7F400000000000, 0x9A130B963A6C115C}, // 1e35\n\t{0x4B9F100000000000, 0xC097CE7BC90715B3}, // 1e36\n\t{0x1E86D40000000000, 0xF0BDC21ABB48DB20}, // 1e37\n\t{0x1314448000000000, 0x96769950B50D88F4}, // 1e38\n\t{0x17D955A000000000, 0xBC143FA4E250EB31}, // 1e39\n\t{0x5DCFAB0800000000, 0xEB194F8E1AE525FD}, // 1e40\n\t{0x5AA1CAE500000000, 0x92EFD1B8D0CF37BE}, // 1e41\n\t{0xF14A3D9E40000000, 0xB7ABC627050305AD}, // 1e42\n\t{0x6D9CCD05D0000000, 0xE596B7B0C643C719}, // 1e43\n\t{0xE4820023A2000000, 0x8F7E32CE7BEA5C6F}, // 1e44\n\t{0xDDA2802C8A800000, 0xB35DBF821AE4F38B}, // 1e45\n\t{0xD50B2037AD200000, 0xE0352F62A19E306E}, // 1e46\n\t{0x4526F422CC340000, 0x8C213D9DA502DE45}, // 1e47\n\t{0x9670B12B7F410000, 0xAF298D050E4395D6}, // 1e48\n\t{0x3C0CDD765F114000, 0xDAF3F04651D47B4C}, // 1e49\n\t{0xA5880A69FB6AC800, 0x88D8762BF324CD0F}, // 1e50\n\t{0x8EEA0D047A457A00, 0xAB0E93B6EFEE0053}, // 1e51\n\t{0x72A4904598D6D880, 0xD5D238A4ABE98068}, // 1e52\n\t{0x47A6DA2B7F864750, 0x85A36366EB71F041}, // 1e53\n\t{0x999090B65F67D924, 0xA70C3C40A64E6C51}, // 1e54\n\t{0xFFF4B4E3F741CF6D, 0xD0CF4B50CFE20765}, // 1e55\n\t{0xBFF8F10E7A8921A4, 0x82818F1281ED449F}, // 1e56\n\t{0xAFF72D52192B6A0D, 0xA321F2D7226895C7}, // 1e57\n\t{0x9BF4F8A69F764490, 0xCBEA6F8CEB02BB39}, // 1e58\n\t{0x02F236D04753D5B4, 0xFEE50B7025C36A08}, // 1e59\n\t{0x01D762422C946590, 0x9F4F2726179A2245}, // 1e60\n\t{0x424D3AD2B7B97EF5, 0xC722F0EF9D80AAD6}, // 1e61\n\t{0xD2E0898765A7DEB2, 0xF8EBAD2B84E0D58B}, // 1e62\n\t{0x63CC55F49F88EB2F, 0x9B934C3B330C8577}, // 1e63\n\t{0x3CBF6B71C76B25FB, 0xC2781F49FFCFA6D5}, // 1e64\n\t{0x8BEF464E3945EF7A, 0xF316271C7FC3908A}, // 1e65\n\t{0x97758BF0E3CBB5AC, 0x97EDD871CFDA3A56}, // 1e66\n\t{0x3D52EEED1CBEA317, 0xBDE94E8E43D0C8EC}, // 1e67\n\t{0x4CA7AAA863EE4BDD, 0xED63A231D4C4FB27}, // 1e68\n\t{0x8FE8CAA93E74EF6A, 0x945E455F24FB1CF8}, // 1e69\n\t{0xB3E2FD538E122B44, 0xB975D6B6EE39E436}, // 1e70\n\t{0x60DBBCA87196B616, 0xE7D34C64A9C85D44}, // 1e71\n\t{0xBC8955E946FE31CD, 0x90E40FBEEA1D3A4A}, // 1e72\n\t{0x6BABAB6398BDBE41, 0xB51D13AEA4A488DD}, // 1e73\n\t{0xC696963C7EED2DD1, 0xE264589A4DCDAB14}, // 1e74\n\t{0xFC1E1DE5CF543CA2, 0x8D7EB76070A08AEC}, // 1e75\n\t{0x3B25A55F43294BCB, 0xB0DE65388CC8ADA8}, // 1e76\n\t{0x49EF0EB713F39EBE, 0xDD15FE86AFFAD912}, // 1e77\n\t{0x6E3569326C784337, 0x8A2DBF142DFCC7AB}, // 1e78\n\t{0x49C2C37F07965404, 0xACB92ED9397BF996}, // 1e79\n\t{0xDC33745EC97BE906, 0xD7E77A8F87DAF7FB}, // 1e80\n\t{0x69A028BB3DED71A3, 0x86F0AC99B4E8DAFD}, // 1e81\n\t{0xC40832EA0D68CE0C, 0xA8ACD7C0222311BC}, // 1e82\n\t{0xF50A3FA490C30190, 0xD2D80DB02AABD62B}, // 1e83\n\t{0x792667C6DA79E0FA, 0x83C7088E1AAB65DB}, // 1e84\n\t{0x577001B891185938, 0xA4B8CAB1A1563F52}, // 1e85\n\t{0xED4C0226B55E6F86, 0xCDE6FD5E09ABCF26}, // 1e86\n\t{0x544F8158315B05B4, 0x80B05E5AC60B6178}, // 1e87\n\t{0x696361AE3DB1C721, 0xA0DC75F1778E39D6}, // 1e88\n\t{0x03BC3A19CD1E38E9, 0xC913936DD571C84C}, // 1e89\n\t{0x04AB48A04065C723, 0xFB5878494ACE3A5F}, // 1e90\n\t{0x62EB0D64283F9C76, 0x9D174B2DCEC0E47B}, // 1e91\n\t{0x3BA5D0BD324F8394, 0xC45D1DF942711D9A}, // 1e92\n\t{0xCA8F44EC7EE36479, 0xF5746577930D6500}, // 1e93\n\t{0x7E998B13CF4E1ECB, 0x9968BF6ABBE85F20}, // 1e94\n\t{0x9E3FEDD8C321A67E, 0xBFC2EF456AE276E8}, // 1e95\n\t{0xC5CFE94EF3EA101E, 0xEFB3AB16C59B14A2}, // 1e96\n\t{0xBBA1F1D158724A12, 0x95D04AEE3B80ECE5}, // 1e97\n\t{0x2A8A6E45AE8EDC97, 0xBB445DA9CA61281F}, // 1e98\n\t{0xF52D09D71A3293BD, 0xEA1575143CF97226}, // 1e99\n\t{0x593C2626705F9C56, 0x924D692CA61BE758}, // 1e100\n\t{0x6F8B2FB00C77836C, 0xB6E0C377CFA2E12E}, // 1e101\n\t{0x0B6DFB9C0F956447, 0xE498F455C38B997A}, // 1e102\n\t{0x4724BD4189BD5EAC, 0x8EDF98B59A373FEC}, // 1e103\n\t{0x58EDEC91EC2CB657, 0xB2977EE300C50FE7}, // 1e104\n\t{0x2F2967B66737E3ED, 0xDF3D5E9BC0F653E1}, // 1e105\n\t{0xBD79E0D20082EE74, 0x8B865B215899F46C}, // 1e106\n\t{0xECD8590680A3AA11, 0xAE67F1E9AEC07187}, // 1e107\n\t{0xE80E6F4820CC9495, 0xDA01EE641A708DE9}, // 1e108\n\t{0x3109058D147FDCDD, 0x884134FE908658B2}, // 1e109\n\t{0xBD4B46F0599FD415, 0xAA51823E34A7EEDE}, // 1e110\n\t{0x6C9E18AC7007C91A, 0xD4E5E2CDC1D1EA96}, // 1e111\n\t{0x03E2CF6BC604DDB0, 0x850FADC09923329E}, // 1e112\n\t{0x84DB8346B786151C, 0xA6539930BF6BFF45}, // 1e113\n\t{0xE612641865679A63, 0xCFE87F7CEF46FF16}, // 1e114\n\t{0x4FCB7E8F3F60C07E, 0x81F14FAE158C5F6E}, // 1e115\n\t{0xE3BE5E330F38F09D, 0xA26DA3999AEF7749}, // 1e116\n\t{0x5CADF5BFD3072CC5, 0xCB090C8001AB551C}, // 1e117\n\t{0x73D9732FC7C8F7F6, 0xFDCB4FA002162A63}, // 1e118\n\t{0x2867E7FDDCDD9AFA, 0x9E9F11C4014DDA7E}, // 1e119\n\t{0xB281E1FD541501B8, 0xC646D63501A1511D}, // 1e120\n\t{0x1F225A7CA91A4226, 0xF7D88BC24209A565}, // 1e121\n\t{0x3375788DE9B06958, 0x9AE757596946075F}, // 1e122\n\t{0x0052D6B1641C83AE, 0xC1A12D2FC3978937}, // 1e123\n\t{0xC0678C5DBD23A49A, 0xF209787BB47D6B84}, // 1e124\n\t{0xF840B7BA963646E0, 0x9745EB4D50CE6332}, // 1e125\n\t{0xB650E5A93BC3D898, 0xBD176620A501FBFF}, // 1e126\n\t{0xA3E51F138AB4CEBE, 0xEC5D3FA8CE427AFF}, // 1e127\n\t{0xC66F336C36B10137, 0x93BA47C980E98CDF}, // 1e128\n\t{0xB80B0047445D4184, 0xB8A8D9BBE123F017}, // 1e129\n\t{0xA60DC059157491E5, 0xE6D3102AD96CEC1D}, // 1e130\n\t{0x87C89837AD68DB2F, 0x9043EA1AC7E41392}, // 1e131\n\t{0x29BABE4598C311FB, 0xB454E4A179DD1877}, // 1e132\n\t{0xF4296DD6FEF3D67A, 0xE16A1DC9D8545E94}, // 1e133\n\t{0x1899E4A65F58660C, 0x8CE2529E2734BB1D}, // 1e134\n\t{0x5EC05DCFF72E7F8F, 0xB01AE745B101E9E4}, // 1e135\n\t{0x76707543F4FA1F73, 0xDC21A1171D42645D}, // 1e136\n\t{0x6A06494A791C53A8, 0x899504AE72497EBA}, // 1e137\n\t{0x0487DB9D17636892, 0xABFA45DA0EDBDE69}, // 1e138\n\t{0x45A9D2845D3C42B6, 0xD6F8D7509292D603}, // 1e139\n\t{0x0B8A2392BA45A9B2, 0x865B86925B9BC5C2}, // 1e140\n\t{0x8E6CAC7768D7141E, 0xA7F26836F282B732}, // 1e141\n\t{0x3207D795430CD926, 0xD1EF0244AF2364FF}, // 1e142\n\t{0x7F44E6BD49E807B8, 0x8335616AED761F1F}, // 1e143\n\t{0x5F16206C9C6209A6, 0xA402B9C5A8D3A6E7}, // 1e144\n\t{0x36DBA887C37A8C0F, 0xCD036837130890A1}, // 1e145\n\t{0xC2494954DA2C9789, 0x802221226BE55A64}, // 1e146\n\t{0xF2DB9BAA10B7BD6C, 0xA02AA96B06DEB0FD}, // 1e147\n\t{0x6F92829494E5ACC7, 0xC83553C5C8965D3D}, // 1e148\n\t{0xCB772339BA1F17F9, 0xFA42A8B73ABBF48C}, // 1e149\n\t{0xFF2A760414536EFB, 0x9C69A97284B578D7}, // 1e150\n\t{0xFEF5138519684ABA, 0xC38413CF25E2D70D}, // 1e151\n\t{0x7EB258665FC25D69, 0xF46518C2EF5B8CD1}, // 1e152\n\t{0xEF2F773FFBD97A61, 0x98BF2F79D5993802}, // 1e153\n\t{0xAAFB550FFACFD8FA, 0xBEEEFB584AFF8603}, // 1e154\n\t{0x95BA2A53F983CF38, 0xEEAABA2E5DBF6784}, // 1e155\n\t{0xDD945A747BF26183, 0x952AB45CFA97A0B2}, // 1e156\n\t{0x94F971119AEEF9E4, 0xBA756174393D88DF}, // 1e157\n\t{0x7A37CD5601AAB85D, 0xE912B9D1478CEB17}, // 1e158\n\t{0xAC62E055C10AB33A, 0x91ABB422CCB812EE}, // 1e159\n\t{0x577B986B314D6009, 0xB616A12B7FE617AA}, // 1e160\n\t{0xED5A7E85FDA0B80B, 0xE39C49765FDF9D94}, // 1e161\n\t{0x14588F13BE847307, 0x8E41ADE9FBEBC27D}, // 1e162\n\t{0x596EB2D8AE258FC8, 0xB1D219647AE6B31C}, // 1e163\n\t{0x6FCA5F8ED9AEF3BB, 0xDE469FBD99A05FE3}, // 1e164\n\t{0x25DE7BB9480D5854, 0x8AEC23D680043BEE}, // 1e165\n\t{0xAF561AA79A10AE6A, 0xADA72CCC20054AE9}, // 1e166\n\t{0x1B2BA1518094DA04, 0xD910F7FF28069DA4}, // 1e167\n\t{0x90FB44D2F05D0842, 0x87AA9AFF79042286}, // 1e168\n\t{0x353A1607AC744A53, 0xA99541BF57452B28}, // 1e169\n\t{0x42889B8997915CE8, 0xD3FA922F2D1675F2}, // 1e170\n\t{0x69956135FEBADA11, 0x847C9B5D7C2E09B7}, // 1e171\n\t{0x43FAB9837E699095, 0xA59BC234DB398C25}, // 1e172\n\t{0x94F967E45E03F4BB, 0xCF02B2C21207EF2E}, // 1e173\n\t{0x1D1BE0EEBAC278F5, 0x8161AFB94B44F57D}, // 1e174\n\t{0x6462D92A69731732, 0xA1BA1BA79E1632DC}, // 1e175\n\t{0x7D7B8F7503CFDCFE, 0xCA28A291859BBF93}, // 1e176\n\t{0x5CDA735244C3D43E, 0xFCB2CB35E702AF78}, // 1e177\n\t{0x3A0888136AFA64A7, 0x9DEFBF01B061ADAB}, // 1e178\n\t{0x088AAA1845B8FDD0, 0xC56BAEC21C7A1916}, // 1e179\n\t{0x8AAD549E57273D45, 0xF6C69A72A3989F5B}, // 1e180\n\t{0x36AC54E2F678864B, 0x9A3C2087A63F6399}, // 1e181\n\t{0x84576A1BB416A7DD, 0xC0CB28A98FCF3C7F}, // 1e182\n\t{0x656D44A2A11C51D5, 0xF0FDF2D3F3C30B9F}, // 1e183\n\t{0x9F644AE5A4B1B325, 0x969EB7C47859E743}, // 1e184\n\t{0x873D5D9F0DDE1FEE, 0xBC4665B596706114}, // 1e185\n\t{0xA90CB506D155A7EA, 0xEB57FF22FC0C7959}, // 1e186\n\t{0x09A7F12442D588F2, 0x9316FF75DD87CBD8}, // 1e187\n\t{0x0C11ED6D538AEB2F, 0xB7DCBF5354E9BECE}, // 1e188\n\t{0x8F1668C8A86DA5FA, 0xE5D3EF282A242E81}, // 1e189\n\t{0xF96E017D694487BC, 0x8FA475791A569D10}, // 1e190\n\t{0x37C981DCC395A9AC, 0xB38D92D760EC4455}, // 1e191\n\t{0x85BBE253F47B1417, 0xE070F78D3927556A}, // 1e192\n\t{0x93956D7478CCEC8E, 0x8C469AB843B89562}, // 1e193\n\t{0x387AC8D1970027B2, 0xAF58416654A6BABB}, // 1e194\n\t{0x06997B05FCC0319E, 0xDB2E51BFE9D0696A}, // 1e195\n\t{0x441FECE3BDF81F03, 0x88FCF317F22241E2}, // 1e196\n\t{0xD527E81CAD7626C3, 0xAB3C2FDDEEAAD25A}, // 1e197\n\t{0x8A71E223D8D3B074, 0xD60B3BD56A5586F1}, // 1e198\n\t{0xF6872D5667844E49, 0x85C7056562757456}, // 1e199\n\t{0xB428F8AC016561DB, 0xA738C6BEBB12D16C}, // 1e200\n\t{0xE13336D701BEBA52, 0xD106F86E69D785C7}, // 1e201\n\t{0xECC0024661173473, 0x82A45B450226B39C}, // 1e202\n\t{0x27F002D7F95D0190, 0xA34D721642B06084}, // 1e203\n\t{0x31EC038DF7B441F4, 0xCC20CE9BD35C78A5}, // 1e204\n\t{0x7E67047175A15271, 0xFF290242C83396CE}, // 1e205\n\t{0x0F0062C6E984D386, 0x9F79A169BD203E41}, // 1e206\n\t{0x52C07B78A3E60868, 0xC75809C42C684DD1}, // 1e207\n\t{0xA7709A56CCDF8A82, 0xF92E0C3537826145}, // 1e208\n\t{0x88A66076400BB691, 0x9BBCC7A142B17CCB}, // 1e209\n\t{0x6ACFF893D00EA435, 0xC2ABF989935DDBFE}, // 1e210\n\t{0x0583F6B8C4124D43, 0xF356F7EBF83552FE}, // 1e211\n\t{0xC3727A337A8B704A, 0x98165AF37B2153DE}, // 1e212\n\t{0x744F18C0592E4C5C, 0xBE1BF1B059E9A8D6}, // 1e213\n\t{0x1162DEF06F79DF73, 0xEDA2EE1C7064130C}, // 1e214\n\t{0x8ADDCB5645AC2BA8, 0x9485D4D1C63E8BE7}, // 1e215\n\t{0x6D953E2BD7173692, 0xB9A74A0637CE2EE1}, // 1e216\n\t{0xC8FA8DB6CCDD0437, 0xE8111C87C5C1BA99}, // 1e217\n\t{0x1D9C9892400A22A2, 0x910AB1D4DB9914A0}, // 1e218\n\t{0x2503BEB6D00CAB4B, 0xB54D5E4A127F59C8}, // 1e219\n\t{0x2E44AE64840FD61D, 0xE2A0B5DC971F303A}, // 1e220\n\t{0x5CEAECFED289E5D2, 0x8DA471A9DE737E24}, // 1e221\n\t{0x7425A83E872C5F47, 0xB10D8E1456105DAD}, // 1e222\n\t{0xD12F124E28F77719, 0xDD50F1996B947518}, // 1e223\n\t{0x82BD6B70D99AAA6F, 0x8A5296FFE33CC92F}, // 1e224\n\t{0x636CC64D1001550B, 0xACE73CBFDC0BFB7B}, // 1e225\n\t{0x3C47F7E05401AA4E, 0xD8210BEFD30EFA5A}, // 1e226\n\t{0x65ACFAEC34810A71, 0x8714A775E3E95C78}, // 1e227\n\t{0x7F1839A741A14D0D, 0xA8D9D1535CE3B396}, // 1e228\n\t{0x1EDE48111209A050, 0xD31045A8341CA07C}, // 1e229\n\t{0x934AED0AAB460432, 0x83EA2B892091E44D}, // 1e230\n\t{0xF81DA84D5617853F, 0xA4E4B66B68B65D60}, // 1e231\n\t{0x36251260AB9D668E, 0xCE1DE40642E3F4B9}, // 1e232\n\t{0xC1D72B7C6B426019, 0x80D2AE83E9CE78F3}, // 1e233\n\t{0xB24CF65B8612F81F, 0xA1075A24E4421730}, // 1e234\n\t{0xDEE033F26797B627, 0xC94930AE1D529CFC}, // 1e235\n\t{0x169840EF017DA3B1, 0xFB9B7CD9A4A7443C}, // 1e236\n\t{0x8E1F289560EE864E, 0x9D412E0806E88AA5}, // 1e237\n\t{0xF1A6F2BAB92A27E2, 0xC491798A08A2AD4E}, // 1e238\n\t{0xAE10AF696774B1DB, 0xF5B5D7EC8ACB58A2}, // 1e239\n\t{0xACCA6DA1E0A8EF29, 0x9991A6F3D6BF1765}, // 1e240\n\t{0x17FD090A58D32AF3, 0xBFF610B0CC6EDD3F}, // 1e241\n\t{0xDDFC4B4CEF07F5B0, 0xEFF394DCFF8A948E}, // 1e242\n\t{0x4ABDAF101564F98E, 0x95F83D0A1FB69CD9}, // 1e243\n\t{0x9D6D1AD41ABE37F1, 0xBB764C4CA7A4440F}, // 1e244\n\t{0x84C86189216DC5ED, 0xEA53DF5FD18D5513}, // 1e245\n\t{0x32FD3CF5B4E49BB4, 0x92746B9BE2F8552C}, // 1e246\n\t{0x3FBC8C33221DC2A1, 0xB7118682DBB66A77}, // 1e247\n\t{0x0FABAF3FEAA5334A, 0xE4D5E82392A40515}, // 1e248\n\t{0x29CB4D87F2A7400E, 0x8F05B1163BA6832D}, // 1e249\n\t{0x743E20E9EF511012, 0xB2C71D5BCA9023F8}, // 1e250\n\t{0x914DA9246B255416, 0xDF78E4B2BD342CF6}, // 1e251\n\t{0x1AD089B6C2F7548E, 0x8BAB8EEFB6409C1A}, // 1e252\n\t{0xA184AC2473B529B1, 0xAE9672ABA3D0C320}, // 1e253\n\t{0xC9E5D72D90A2741E, 0xDA3C0F568CC4F3E8}, // 1e254\n\t{0x7E2FA67C7A658892, 0x8865899617FB1871}, // 1e255\n\t{0xDDBB901B98FEEAB7, 0xAA7EEBFB9DF9DE8D}, // 1e256\n\t{0x552A74227F3EA565, 0xD51EA6FA85785631}, // 1e257\n\t{0xD53A88958F87275F, 0x8533285C936B35DE}, // 1e258\n\t{0x8A892ABAF368F137, 0xA67FF273B8460356}, // 1e259\n\t{0x2D2B7569B0432D85, 0xD01FEF10A657842C}, // 1e260\n\t{0x9C3B29620E29FC73, 0x8213F56A67F6B29B}, // 1e261\n\t{0x8349F3BA91B47B8F, 0xA298F2C501F45F42}, // 1e262\n\t{0x241C70A936219A73, 0xCB3F2F7642717713}, // 1e263\n\t{0xED238CD383AA0110, 0xFE0EFB53D30DD4D7}, // 1e264\n\t{0xF4363804324A40AA, 0x9EC95D1463E8A506}, // 1e265\n\t{0xB143C6053EDCD0D5, 0xC67BB4597CE2CE48}, // 1e266\n\t{0xDD94B7868E94050A, 0xF81AA16FDC1B81DA}, // 1e267\n\t{0xCA7CF2B4191C8326, 0x9B10A4E5E9913128}, // 1e268\n\t{0xFD1C2F611F63A3F0, 0xC1D4CE1F63F57D72}, // 1e269\n\t{0xBC633B39673C8CEC, 0xF24A01A73CF2DCCF}, // 1e270\n\t{0xD5BE0503E085D813, 0x976E41088617CA01}, // 1e271\n\t{0x4B2D8644D8A74E18, 0xBD49D14AA79DBC82}, // 1e272\n\t{0xDDF8E7D60ED1219E, 0xEC9C459D51852BA2}, // 1e273\n\t{0xCABB90E5C942B503, 0x93E1AB8252F33B45}, // 1e274\n\t{0x3D6A751F3B936243, 0xB8DA1662E7B00A17}, // 1e275\n\t{0x0CC512670A783AD4, 0xE7109BFBA19C0C9D}, // 1e276\n\t{0x27FB2B80668B24C5, 0x906A617D450187E2}, // 1e277\n\t{0xB1F9F660802DEDF6, 0xB484F9DC9641E9DA}, // 1e278\n\t{0x5E7873F8A0396973, 0xE1A63853BBD26451}, // 1e279\n\t{0xDB0B487B6423E1E8, 0x8D07E33455637EB2}, // 1e280\n\t{0x91CE1A9A3D2CDA62, 0xB049DC016ABC5E5F}, // 1e281\n\t{0x7641A140CC7810FB, 0xDC5C5301C56B75F7}, // 1e282\n\t{0xA9E904C87FCB0A9D, 0x89B9B3E11B6329BA}, // 1e283\n\t{0x546345FA9FBDCD44, 0xAC2820D9623BF429}, // 1e284\n\t{0xA97C177947AD4095, 0xD732290FBACAF133}, // 1e285\n\t{0x49ED8EABCCCC485D, 0x867F59A9D4BED6C0}, // 1e286\n\t{0x5C68F256BFFF5A74, 0xA81F301449EE8C70}, // 1e287\n\t{0x73832EEC6FFF3111, 0xD226FC195C6A2F8C}, // 1e288\n\t{0xC831FD53C5FF7EAB, 0x83585D8FD9C25DB7}, // 1e289\n\t{0xBA3E7CA8B77F5E55, 0xA42E74F3D032F525}, // 1e290\n\t{0x28CE1BD2E55F35EB, 0xCD3A1230C43FB26F}, // 1e291\n\t{0x7980D163CF5B81B3, 0x80444B5E7AA7CF85}, // 1e292\n\t{0xD7E105BCC332621F, 0xA0555E361951C366}, // 1e293\n\t{0x8DD9472BF3FEFAA7, 0xC86AB5C39FA63440}, // 1e294\n\t{0xB14F98F6F0FEB951, 0xFA856334878FC150}, // 1e295\n\t{0x6ED1BF9A569F33D3, 0x9C935E00D4B9D8D2}, // 1e296\n\t{0x0A862F80EC4700C8, 0xC3B8358109E84F07}, // 1e297\n\t{0xCD27BB612758C0FA, 0xF4A642E14C6262C8}, // 1e298\n\t{0x8038D51CB897789C, 0x98E7E9CCCFBD7DBD}, // 1e299\n\t{0xE0470A63E6BD56C3, 0xBF21E44003ACDD2C}, // 1e300\n\t{0x1858CCFCE06CAC74, 0xEEEA5D5004981478}, // 1e301\n\t{0x0F37801E0C43EBC8, 0x95527A5202DF0CCB}, // 1e302\n\t{0xD30560258F54E6BA, 0xBAA718E68396CFFD}, // 1e303\n\t{0x47C6B82EF32A2069, 0xE950DF20247C83FD}, // 1e304\n\t{0x4CDC331D57FA5441, 0x91D28B7416CDD27E}, // 1e305\n\t{0xE0133FE4ADF8E952, 0xB6472E511C81471D}, // 1e306\n\t{0x58180FDDD97723A6, 0xE3D8F9E563A198E5}, // 1e307\n\t{0x570F09EAA7EA7648, 0x8E679C2F5E44FF8F}, // 1e308\n\t{0x2CD2CC6551E513DA, 0xB201833B35D63F73}, // 1e309\n\t{0xF8077F7EA65E58D1, 0xDE81E40A034BCF4F}, // 1e310\n\t{0xFB04AFAF27FAF782, 0x8B112E86420F6191}, // 1e311\n\t{0x79C5DB9AF1F9B563, 0xADD57A27D29339F6}, // 1e312\n\t{0x18375281AE7822BC, 0xD94AD8B1C7380874}, // 1e313\n\t{0x8F2293910D0B15B5, 0x87CEC76F1C830548}, // 1e314\n\t{0xB2EB3875504DDB22, 0xA9C2794AE3A3C69A}, // 1e315\n\t{0x5FA60692A46151EB, 0xD433179D9C8CB841}, // 1e316\n\t{0xDBC7C41BA6BCD333, 0x849FEEC281D7F328}, // 1e317\n\t{0x12B9B522906C0800, 0xA5C7EA73224DEFF3}, // 1e318\n\t{0xD768226B34870A00, 0xCF39E50FEAE16BEF}, // 1e319\n\t{0xE6A1158300D46640, 0x81842F29F2CCE375}, // 1e320\n\t{0x60495AE3C1097FD0, 0xA1E53AF46F801C53}, // 1e321\n\t{0x385BB19CB14BDFC4, 0xCA5E89B18B602368}, // 1e322\n\t{0x46729E03DD9ED7B5, 0xFCF62C1DEE382C42}, // 1e323\n\t{0x6C07A2C26A8346D1, 0x9E19DB92B4E31BA9}, // 1e324\n\t{0xC7098B7305241885, 0xC5A05277621BE293}, // 1e325\n\t{0xB8CBEE4FC66D1EA7, 0xF70867153AA2DB38}, // 1e326\n\t{0x737F74F1DC043328, 0x9A65406D44A5C903}, // 1e327\n\t{0x505F522E53053FF2, 0xC0FE908895CF3B44}, // 1e328\n\t{0x647726B9E7C68FEF, 0xF13E34AABB430A15}, // 1e329\n\t{0x5ECA783430DC19F5, 0x96C6E0EAB509E64D}, // 1e330\n\t{0xB67D16413D132072, 0xBC789925624C5FE0}, // 1e331\n\t{0xE41C5BD18C57E88F, 0xEB96BF6EBADF77D8}, // 1e332\n\t{0x8E91B962F7B6F159, 0x933E37A534CBAAE7}, // 1e333\n\t{0x723627BBB5A4ADB0, 0xB80DC58E81FE95A1}, // 1e334\n\t{0xCEC3B1AAA30DD91C, 0xE61136F2227E3B09}, // 1e335\n\t{0x213A4F0AA5E8A7B1, 0x8FCAC257558EE4E6}, // 1e336\n\t{0xA988E2CD4F62D19D, 0xB3BD72ED2AF29E1F}, // 1e337\n\t{0x93EB1B80A33B8605, 0xE0ACCFA875AF45A7}, // 1e338\n\t{0xBC72F130660533C3, 0x8C6C01C9498D8B88}, // 1e339\n\t{0xEB8FAD7C7F8680B4, 0xAF87023B9BF0EE6A}, // 1e340\n\t{0xA67398DB9F6820E1, 0xDB68C2CA82ED2A05}, // 1e341\n\t{0x88083F8943A1148C, 0x892179BE91D43A43}, // 1e342\n\t{0x6A0A4F6B948959B0, 0xAB69D82E364948D4}, // 1e343\n\t{0x848CE34679ABB01C, 0xD6444E39C3DB9B09}, // 1e344\n\t{0xF2D80E0C0C0B4E11, 0x85EAB0E41A6940E5}, // 1e345\n\t{0x6F8E118F0F0E2195, 0xA7655D1D2103911F}, // 1e346\n\t{0x4B7195F2D2D1A9FB, 0xD13EB46469447567}, // 1e347\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ryu", + "path": "gno.land/p/demo/json/ryu", + "files": [ + { + "name": "floatconv.gno", + "body": "// Copyright 2018 Ulf Adams\n// Modifications copyright 2019 Caleb Spare\n//\n// The contents of this file may be used under the terms of the Apache License,\n// Version 2.0.\n//\n// (See accompanying file LICENSE or copy at\n// http://www.apache.org/licenses/LICENSE-2.0)\n//\n// Unless required by applicable law or agreed to in writing, this software\n// is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n// KIND, either express or implied.\n//\n// The code in this file is part of a Go translation of the C code originally written by\n// Ulf Adams, which can be found at https://github.com/ulfjack/ryu. The original source\n// code is licensed under the Apache License 2.0. This code is a derivative work thereof,\n// adapted and modified to meet the specifications of the Gno language project.\n//\n// original Go implementation can be found at https://github.com/cespare/ryu.\n//\n// Please note that the modifications are also under the Apache License 2.0 unless\n// otherwise specified.\n\n// Package ryu implements the Ryu algorithm for quickly converting floating\n// point numbers into strings.\npackage ryu\n\nimport (\n\t\"math\"\n)\n\nconst (\n\tmantBits32 = 23\n\texpBits32 = 8\n\tbias32 = 127\n\n\tmantBits64 = 52\n\texpBits64 = 11\n\tbias64 = 1023\n)\n\n// FormatFloat64 converts a 64-bit floating point number f to a string.\n// It behaves like strconv.FormatFloat(f, 'e', -1, 64).\nfunc FormatFloat64(f float64) string {\n\tb := make([]byte, 0, 24)\n\tb = AppendFloat64(b, f)\n\treturn string(b)\n}\n\n// AppendFloat64 appends the string form of the 64-bit floating point number f,\n// as generated by FormatFloat64, to b and returns the extended buffer.\nfunc AppendFloat64(b []byte, f float64) []byte {\n\t// Step 1: Decode the floating-point number.\n\t// Unify normalized and subnormal cases.\n\tu := math.Float64bits(f)\n\tneg := u\u003e\u003e(mantBits64+expBits64) != 0\n\tmant := u \u0026 (uint64(1)\u003c\u003cmantBits64 - 1)\n\texp := (u \u003e\u003e mantBits64) \u0026 (uint64(1)\u003c\u003cexpBits64 - 1)\n\n\t// Exit early for easy cases.\n\tif exp == uint64(1)\u003c\u003cexpBits64-1 || (exp == 0 \u0026\u0026 mant == 0) {\n\t\treturn appendSpecial(b, neg, exp == 0, mant == 0)\n\t}\n\n\td, ok := float64ToDecimalExactInt(mant, exp)\n\tif !ok {\n\t\td = float64ToDecimal(mant, exp)\n\t}\n\treturn d.append(b, neg)\n}\n\nfunc appendSpecial(b []byte, neg, expZero, mantZero bool) []byte {\n\tif !mantZero {\n\t\treturn append(b, \"NaN\"...)\n\t}\n\tif !expZero {\n\t\tif neg {\n\t\t\treturn append(b, \"-Inf\"...)\n\t\t}\n\n\t\treturn append(b, \"+Inf\"...)\n\t}\n\n\tif neg {\n\t\tb = append(b, '-')\n\t}\n\treturn append(b, \"0e+00\"...)\n}\n\nfunc boolToInt(b bool) int {\n\tif b {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc boolToUint32(b bool) uint32 {\n\tif b {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc boolToUint64(b bool) uint64 {\n\tif b {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc assert(t bool, msg string) {\n\tif !t {\n\t\tpanic(msg)\n\t}\n}\n\n// log10Pow2 returns floor(log_10(2^e)).\nfunc log10Pow2(e int32) uint32 {\n\t// The first value this approximation fails for is 2^1651\n\t// which is just greater than 10^297.\n\tassert(e \u003e= 0, \"e \u003e= 0\")\n\tassert(e \u003c= 1650, \"e \u003c= 1650\")\n\treturn (uint32(e) * 78913) \u003e\u003e 18\n}\n\n// log10Pow5 returns floor(log_10(5^e)).\nfunc log10Pow5(e int32) uint32 {\n\t// The first value this approximation fails for is 5^2621\n\t// which is just greater than 10^1832.\n\tassert(e \u003e= 0, \"e \u003e= 0\")\n\tassert(e \u003c= 2620, \"e \u003c= 2620\")\n\treturn (uint32(e) * 732923) \u003e\u003e 20\n}\n\n// pow5Bits returns ceil(log_2(5^e)), or else 1 if e==0.\nfunc pow5Bits(e int32) int32 {\n\t// This approximation works up to the point that the multiplication\n\t// overflows at e = 3529. If the multiplication were done in 64 bits,\n\t// it would fail at 5^4004 which is just greater than 2^9297.\n\tassert(e \u003e= 0, \"e \u003e= 0\")\n\tassert(e \u003c= 3528, \"e \u003c= 3528\")\n\treturn int32((uint32(e)*1217359)\u003e\u003e19 + 1)\n}\n" + }, + { + "name": "floatconv_test.gno", + "body": "package ryu\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc TestFormatFloat64(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tvalue float64\n\t\texpected string\n\t}{\n\t\t{\"positive infinity\", math.Inf(1), \"+Inf\"},\n\t\t{\"negative infinity\", math.Inf(-1), \"-Inf\"},\n\t\t{\"NaN\", math.NaN(), \"NaN\"},\n\t\t{\"zero\", 0.0, \"0e+00\"},\n\t\t{\"negative zero\", -0.0, \"0e+00\"},\n\t\t{\"positive number\", 3.14159, \"3.14159e+00\"},\n\t\t{\"negative number\", -2.71828, \"-2.71828e+00\"},\n\t\t{\"very small number\", 1.23e-20, \"1.23e-20\"},\n\t\t{\"very large number\", 1.23e+20, \"1.23e+20\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tresult := FormatFloat64(test.value)\n\t\t\tif result != test.expected {\n\t\t\t\tt.Errorf(\"FormatFloat64(%v) = %q, expected %q\", test.value, result, test.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "ryu64.gno", + "body": "package ryu\n\nimport (\n\t\"math/bits\"\n)\n\ntype uint128 struct {\n\tlo uint64\n\thi uint64\n}\n\n// dec64 is a floating decimal type representing m * 10^e.\ntype dec64 struct {\n\tm uint64\n\te int32\n}\n\nfunc (d dec64) append(b []byte, neg bool) []byte {\n\t// Step 5: Print the decimal representation.\n\tif neg {\n\t\tb = append(b, '-')\n\t}\n\n\tout := d.m\n\toutLen := decimalLen64(out)\n\tbufLen := outLen\n\tif bufLen \u003e 1 {\n\t\tbufLen++ // extra space for '.'\n\t}\n\n\t// Print the decimal digits.\n\tn := len(b)\n\tif cap(b)-len(b) \u003e= bufLen {\n\t\t// Avoid function call in the common case.\n\t\tb = b[:len(b)+bufLen]\n\t} else {\n\t\tb = append(b, make([]byte, bufLen)...)\n\t}\n\n\t// Avoid expensive 64-bit divisions.\n\t// We have at most 17 digits, and uint32 can store 9 digits.\n\t// If the output doesn't fit into a uint32, cut off 8 digits\n\t// so the rest will fit into a uint32.\n\tvar i int\n\tif out\u003e\u003e32 \u003e 0 {\n\t\tvar out32 uint32\n\t\tout, out32 = out/1e8, uint32(out%1e8)\n\t\tfor ; i \u003c 8; i++ {\n\t\t\tb[n+outLen-i] = '0' + byte(out32%10)\n\t\t\tout32 /= 10\n\t\t}\n\t}\n\tout32 := uint32(out)\n\tfor ; i \u003c outLen-1; i++ {\n\t\tb[n+outLen-i] = '0' + byte(out32%10)\n\t\tout32 /= 10\n\t}\n\tb[n] = '0' + byte(out32%10)\n\n\t// Print the '.' if needed.\n\tif outLen \u003e 1 {\n\t\tb[n+1] = '.'\n\t}\n\n\t// Print the exponent.\n\tb = append(b, 'e')\n\texp := d.e + int32(outLen) - 1\n\tif exp \u003c 0 {\n\t\tb = append(b, '-')\n\t\texp = -exp\n\t} else {\n\t\t// Unconditionally print a + here to match strconv's formatting.\n\t\tb = append(b, '+')\n\t}\n\t// Always print at least two digits to match strconv's formatting.\n\td2 := exp % 10\n\texp /= 10\n\td1 := exp % 10\n\td0 := exp / 10\n\tif d0 \u003e 0 {\n\t\tb = append(b, '0'+byte(d0))\n\t}\n\tb = append(b, '0'+byte(d1), '0'+byte(d2))\n\n\treturn b\n}\n\nfunc float64ToDecimalExactInt(mant, exp uint64) (d dec64, ok bool) {\n\te := exp - bias64\n\tif e \u003e mantBits64 {\n\t\treturn d, false\n\t}\n\tshift := mantBits64 - e\n\tmant |= 1 \u003c\u003c mantBits64 // implicit 1\n\td.m = mant \u003e\u003e shift\n\tif d.m\u003c\u003cshift != mant {\n\t\treturn d, false\n\t}\n\n\tfor d.m%10 == 0 {\n\t\td.m /= 10\n\t\td.e++\n\t}\n\treturn d, true\n}\n\nfunc float64ToDecimal(mant, exp uint64) dec64 {\n\tvar e2 int32\n\tvar m2 uint64\n\tif exp == 0 {\n\t\t// We subtract 2 so that the bounds computation has\n\t\t// 2 additional bits.\n\t\te2 = 1 - bias64 - mantBits64 - 2\n\t\tm2 = mant\n\t} else {\n\t\te2 = int32(exp) - bias64 - mantBits64 - 2\n\t\tm2 = uint64(1)\u003c\u003cmantBits64 | mant\n\t}\n\teven := m2\u00261 == 0\n\tacceptBounds := even\n\n\t// Step 2: Determine the interval of valid decimal representations.\n\tmv := 4 * m2\n\tmmShift := boolToUint64(mant != 0 || exp \u003c= 1)\n\t// We would compute mp and mm like this:\n\t// mp := 4 * m2 + 2;\n\t// mm := mv - 1 - mmShift;\n\n\t// Step 3: Convert to a decimal power base uing 128-bit arithmetic.\n\tvar (\n\t\tvr, vp, vm uint64\n\t\te10 int32\n\t\tvmIsTrailingZeros bool\n\t\tvrIsTrailingZeros bool\n\t)\n\tif e2 \u003e= 0 {\n\t\t// This expression is slightly faster than max(0, log10Pow2(e2) - 1).\n\t\tq := log10Pow2(e2) - boolToUint32(e2 \u003e 3)\n\t\te10 = int32(q)\n\t\tk := pow5InvNumBits64 + pow5Bits(int32(q)) - 1\n\t\ti := -e2 + int32(q) + k\n\t\tmul := pow5InvSplit64[q]\n\t\tvr = mulShift64(4*m2, mul, i)\n\t\tvp = mulShift64(4*m2+2, mul, i)\n\t\tvm = mulShift64(4*m2-1-mmShift, mul, i)\n\t\tif q \u003c= 21 {\n\t\t\t// This should use q \u003c= 22, but I think 21 is also safe.\n\t\t\t// Smaller values may still be safe, but it's more\n\t\t\t// difficult to reason about them. Only one of mp, mv,\n\t\t\t// and mm can be a multiple of 5, if any.\n\t\t\tif mv%5 == 0 {\n\t\t\t\tvrIsTrailingZeros = multipleOfPowerOfFive64(mv, q)\n\t\t\t} else if acceptBounds {\n\t\t\t\t// Same as min(e2 + (^mm \u0026 1), pow5Factor64(mm)) \u003e= q\n\t\t\t\t// \u003c=\u003e e2 + (^mm \u0026 1) \u003e= q \u0026\u0026 pow5Factor64(mm) \u003e= q\n\t\t\t\t// \u003c=\u003e true \u0026\u0026 pow5Factor64(mm) \u003e= q, since e2 \u003e= q.\n\t\t\t\tvmIsTrailingZeros = multipleOfPowerOfFive64(mv-1-mmShift, q)\n\t\t\t} else if multipleOfPowerOfFive64(mv+2, q) {\n\t\t\t\tvp--\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// This expression is slightly faster than max(0, log10Pow5(-e2) - 1).\n\t\tq := log10Pow5(-e2) - boolToUint32(-e2 \u003e 1)\n\t\te10 = int32(q) + e2\n\t\ti := -e2 - int32(q)\n\t\tk := pow5Bits(i) - pow5NumBits64\n\t\tj := int32(q) - k\n\t\tmul := pow5Split64[i]\n\t\tvr = mulShift64(4*m2, mul, j)\n\t\tvp = mulShift64(4*m2+2, mul, j)\n\t\tvm = mulShift64(4*m2-1-mmShift, mul, j)\n\t\tif q \u003c= 1 {\n\t\t\t// {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits.\n\t\t\t// mv = 4 * m2, so it always has at least two trailing 0 bits.\n\t\t\tvrIsTrailingZeros = true\n\t\t\tif acceptBounds {\n\t\t\t\t// mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1.\n\t\t\t\tvmIsTrailingZeros = mmShift == 1\n\t\t\t} else {\n\t\t\t\t// mp = mv + 2, so it always has at least one trailing 0 bit.\n\t\t\t\tvp--\n\t\t\t}\n\t\t} else if q \u003c 63 { // TODO(ulfjack/cespare): Use a tighter bound here.\n\t\t\t// We need to compute min(ntz(mv), pow5Factor64(mv) - e2) \u003e= q - 1\n\t\t\t// \u003c=\u003e ntz(mv) \u003e= q - 1 \u0026\u0026 pow5Factor64(mv) - e2 \u003e= q - 1\n\t\t\t// \u003c=\u003e ntz(mv) \u003e= q - 1 (e2 is negative and -e2 \u003e= q)\n\t\t\t// \u003c=\u003e (mv \u0026 ((1 \u003c\u003c (q - 1)) - 1)) == 0\n\t\t\t// We also need to make sure that the left shift does not overflow.\n\t\t\tvrIsTrailingZeros = multipleOfPowerOfTwo64(mv, q-1)\n\t\t}\n\t}\n\n\t// Step 4: Find the shortest decimal representation\n\t// in the interval of valid representations.\n\tvar removed int32\n\tvar lastRemovedDigit uint8\n\tvar out uint64\n\t// On average, we remove ~2 digits.\n\tif vmIsTrailingZeros || vrIsTrailingZeros {\n\t\t// General case, which happens rarely (~0.7%).\n\t\tfor {\n\t\t\tvpDiv10 := vp / 10\n\t\t\tvmDiv10 := vm / 10\n\t\t\tif vpDiv10 \u003c= vmDiv10 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvmMod10 := vm % 10\n\t\t\tvrDiv10 := vr / 10\n\t\t\tvrMod10 := vr % 10\n\t\t\tvmIsTrailingZeros = vmIsTrailingZeros \u0026\u0026 vmMod10 == 0\n\t\t\tvrIsTrailingZeros = vrIsTrailingZeros \u0026\u0026 lastRemovedDigit == 0\n\t\t\tlastRemovedDigit = uint8(vrMod10)\n\t\t\tvr = vrDiv10\n\t\t\tvp = vpDiv10\n\t\t\tvm = vmDiv10\n\t\t\tremoved++\n\t\t}\n\t\tif vmIsTrailingZeros {\n\t\t\tfor {\n\t\t\t\tvmDiv10 := vm / 10\n\t\t\t\tvmMod10 := vm % 10\n\t\t\t\tif vmMod10 != 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tvpDiv10 := vp / 10\n\t\t\t\tvrDiv10 := vr / 10\n\t\t\t\tvrMod10 := vr % 10\n\t\t\t\tvrIsTrailingZeros = vrIsTrailingZeros \u0026\u0026 lastRemovedDigit == 0\n\t\t\t\tlastRemovedDigit = uint8(vrMod10)\n\t\t\t\tvr = vrDiv10\n\t\t\t\tvp = vpDiv10\n\t\t\t\tvm = vmDiv10\n\t\t\t\tremoved++\n\t\t\t}\n\t\t}\n\t\tif vrIsTrailingZeros \u0026\u0026 lastRemovedDigit == 5 \u0026\u0026 vr%2 == 0 {\n\t\t\t// Round even if the exact number is .....50..0.\n\t\t\tlastRemovedDigit = 4\n\t\t}\n\t\tout = vr\n\t\t// We need to take vr + 1 if vr is outside bounds\n\t\t// or we need to round up.\n\t\tif (vr == vm \u0026\u0026 (!acceptBounds || !vmIsTrailingZeros)) || lastRemovedDigit \u003e= 5 {\n\t\t\tout++\n\t\t}\n\t} else {\n\t\t// Specialized for the common case (~99.3%).\n\t\t// Percentages below are relative to this.\n\t\troundUp := false\n\t\tfor vp/100 \u003e vm/100 {\n\t\t\t// Optimization: remove two digits at a time (~86.2%).\n\t\t\troundUp = vr%100 \u003e= 50\n\t\t\tvr /= 100\n\t\t\tvp /= 100\n\t\t\tvm /= 100\n\t\t\tremoved += 2\n\t\t}\n\t\t// Loop iterations below (approximately), without optimization above:\n\t\t// 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02%\n\t\t// Loop iterations below (approximately), with optimization above:\n\t\t// 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02%\n\t\tfor vp/10 \u003e vm/10 {\n\t\t\troundUp = vr%10 \u003e= 5\n\t\t\tvr /= 10\n\t\t\tvp /= 10\n\t\t\tvm /= 10\n\t\t\tremoved++\n\t\t}\n\t\t// We need to take vr + 1 if vr is outside bounds\n\t\t// or we need to round up.\n\t\tout = vr + boolToUint64(vr == vm || roundUp)\n\t}\n\n\treturn dec64{m: out, e: e10 + removed}\n}\n\nvar powersOf10 = [...]uint64{\n\t1e0,\n\t1e1,\n\t1e2,\n\t1e3,\n\t1e4,\n\t1e5,\n\t1e6,\n\t1e7,\n\t1e8,\n\t1e9,\n\t1e10,\n\t1e11,\n\t1e12,\n\t1e13,\n\t1e14,\n\t1e15,\n\t1e16,\n\t1e17,\n\t// We only need to find the length of at most 17 digit numbers.\n}\n\nfunc decimalLen64(u uint64) int {\n\t// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10\n\tlog2 := 64 - bits.LeadingZeros64(u) - 1\n\tt := (log2 + 1) * 1233 \u003e\u003e 12\n\treturn t - boolToInt(u \u003c powersOf10[t]) + 1\n}\n\nfunc mulShift64(m uint64, mul uint128, shift int32) uint64 {\n\thihi, hilo := bits.Mul64(m, mul.hi)\n\tlohi, _ := bits.Mul64(m, mul.lo)\n\tsum := uint128{hi: hihi, lo: lohi + hilo}\n\tif sum.lo \u003c lohi {\n\t\tsum.hi++ // overflow\n\t}\n\treturn shiftRight128(sum, shift-64)\n}\n\nfunc shiftRight128(v uint128, shift int32) uint64 {\n\t// The shift value is always modulo 64.\n\t// In the current implementation of the 64-bit version\n\t// of Ryu, the shift value is always \u003c 64.\n\t// (It is in the range [2, 59].)\n\t// Check this here in case a future change requires larger shift\n\t// values. In this case this function needs to be adjusted.\n\tassert(shift \u003c 64, \"shift \u003c 64\")\n\treturn (v.hi \u003c\u003c uint64(64-shift)) | (v.lo \u003e\u003e uint(shift))\n}\n\nfunc pow5Factor64(v uint64) uint32 {\n\tfor n := uint32(0); ; n++ {\n\t\tq, r := v/5, v%5\n\t\tif r != 0 {\n\t\t\treturn n\n\t\t}\n\t\tv = q\n\t}\n}\n\nfunc multipleOfPowerOfFive64(v uint64, p uint32) bool {\n\treturn pow5Factor64(v) \u003e= p\n}\n\nfunc multipleOfPowerOfTwo64(v uint64, p uint32) bool {\n\treturn uint32(bits.TrailingZeros64(v)) \u003e= p\n}\n" + }, + { + "name": "table.gno", + "body": "// Code generated by running \"go generate\". DO NOT EDIT.\n\n// Copyright 2018 Ulf Adams\n// Modifications copyright 2019 Caleb Spare\n//\n// The contents of this file may be used under the terms of the Apache License,\n// Version 2.0.\n//\n// (See accompanying file LICENSE or copy at\n// http://www.apache.org/licenses/LICENSE-2.0)\n//\n// Unless required by applicable law or agreed to in writing, this software\n// is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n// KIND, either express or implied.\n//\n// The code in this file is part of a Go translation of the C code written by\n// Ulf Adams which may be found at https://github.com/ulfjack/ryu. That source\n// code is licensed under Apache 2.0 and this code is derivative work thereof.\n\npackage ryu\n\nconst pow5NumBits32 = 61\n\nvar pow5Split32 = [...]uint64{\n\t1152921504606846976, 1441151880758558720, 1801439850948198400, 2251799813685248000,\n\t1407374883553280000, 1759218604441600000, 2199023255552000000, 1374389534720000000,\n\t1717986918400000000, 2147483648000000000, 1342177280000000000, 1677721600000000000,\n\t2097152000000000000, 1310720000000000000, 1638400000000000000, 2048000000000000000,\n\t1280000000000000000, 1600000000000000000, 2000000000000000000, 1250000000000000000,\n\t1562500000000000000, 1953125000000000000, 1220703125000000000, 1525878906250000000,\n\t1907348632812500000, 1192092895507812500, 1490116119384765625, 1862645149230957031,\n\t1164153218269348144, 1455191522836685180, 1818989403545856475, 2273736754432320594,\n\t1421085471520200371, 1776356839400250464, 2220446049250313080, 1387778780781445675,\n\t1734723475976807094, 2168404344971008868, 1355252715606880542, 1694065894508600678,\n\t2117582368135750847, 1323488980084844279, 1654361225106055349, 2067951531382569187,\n\t1292469707114105741, 1615587133892632177, 2019483917365790221,\n}\n\nconst pow5InvNumBits32 = 59\n\nvar pow5InvSplit32 = [...]uint64{\n\t576460752303423489, 461168601842738791, 368934881474191033, 295147905179352826,\n\t472236648286964522, 377789318629571618, 302231454903657294, 483570327845851670,\n\t386856262276681336, 309485009821345069, 495176015714152110, 396140812571321688,\n\t316912650057057351, 507060240091291761, 405648192073033409, 324518553658426727,\n\t519229685853482763, 415383748682786211, 332306998946228969, 531691198313966350,\n\t425352958651173080, 340282366920938464, 544451787073501542, 435561429658801234,\n\t348449143727040987, 557518629963265579, 446014903970612463, 356811923176489971,\n\t570899077082383953, 456719261665907162, 365375409332725730,\n}\n\nconst pow5NumBits64 = 121\n\nvar pow5Split64 = [...]uint128{\n\t{0, 72057594037927936},\n\t{0, 90071992547409920},\n\t{0, 112589990684262400},\n\t{0, 140737488355328000},\n\t{0, 87960930222080000},\n\t{0, 109951162777600000},\n\t{0, 137438953472000000},\n\t{0, 85899345920000000},\n\t{0, 107374182400000000},\n\t{0, 134217728000000000},\n\t{0, 83886080000000000},\n\t{0, 104857600000000000},\n\t{0, 131072000000000000},\n\t{0, 81920000000000000},\n\t{0, 102400000000000000},\n\t{0, 128000000000000000},\n\t{0, 80000000000000000},\n\t{0, 100000000000000000},\n\t{0, 125000000000000000},\n\t{0, 78125000000000000},\n\t{0, 97656250000000000},\n\t{0, 122070312500000000},\n\t{0, 76293945312500000},\n\t{0, 95367431640625000},\n\t{0, 119209289550781250},\n\t{4611686018427387904, 74505805969238281},\n\t{10376293541461622784, 93132257461547851},\n\t{8358680908399640576, 116415321826934814},\n\t{612489549322387456, 72759576141834259},\n\t{14600669991935148032, 90949470177292823},\n\t{13639151471491547136, 113686837721616029},\n\t{3213881284082270208, 142108547152020037},\n\t{4314518811765112832, 88817841970012523},\n\t{781462496279003136, 111022302462515654},\n\t{10200200157203529728, 138777878078144567},\n\t{13292654125893287936, 86736173798840354},\n\t{7392445620511834112, 108420217248550443},\n\t{4628871007212404736, 135525271560688054},\n\t{16728102434789916672, 84703294725430033},\n\t{7075069988205232128, 105879118406787542},\n\t{18067209522111315968, 132348898008484427},\n\t{8986162942105878528, 82718061255302767},\n\t{6621017659204960256, 103397576569128459},\n\t{3664586055578812416, 129246970711410574},\n\t{16125424340018921472, 80779356694631608},\n\t{1710036351314100224, 100974195868289511},\n\t{15972603494424788992, 126217744835361888},\n\t{9982877184015493120, 78886090522101180},\n\t{12478596480019366400, 98607613152626475},\n\t{10986559581596820096, 123259516440783094},\n\t{2254913720070624656, 77037197775489434},\n\t{12042014186943056628, 96296497219361792},\n\t{15052517733678820785, 120370621524202240},\n\t{9407823583549262990, 75231638452626400},\n\t{11759779479436578738, 94039548065783000},\n\t{14699724349295723422, 117549435082228750},\n\t{4575641699882439235, 73468396926392969},\n\t{10331238143280436948, 91835496157991211},\n\t{8302361660673158281, 114794370197489014},\n\t{1154580038986672043, 143492962746861268},\n\t{9944984561221445835, 89683101716788292},\n\t{12431230701526807293, 112103877145985365},\n\t{1703980321626345405, 140129846432481707},\n\t{17205888765512323542, 87581154020301066},\n\t{12283988920035628619, 109476442525376333},\n\t{1519928094762372062, 136845553156720417},\n\t{12479170105294952299, 85528470722950260},\n\t{15598962631618690374, 106910588403687825},\n\t{5663645234241199255, 133638235504609782},\n\t{17374836326682913246, 83523897190381113},\n\t{7883487353071477846, 104404871487976392},\n\t{9854359191339347308, 130506089359970490},\n\t{10770660513014479971, 81566305849981556},\n\t{13463325641268099964, 101957882312476945},\n\t{2994098996302961243, 127447352890596182},\n\t{15706369927971514489, 79654595556622613},\n\t{5797904354682229399, 99568244445778267},\n\t{2635694424925398845, 124460305557222834},\n\t{6258995034005762182, 77787690973264271},\n\t{3212057774079814824, 97234613716580339},\n\t{17850130272881932242, 121543267145725423},\n\t{18073860448192289507, 75964541966078389},\n\t{8757267504958198172, 94955677457597987},\n\t{6334898362770359811, 118694596821997484},\n\t{13182683513586250689, 74184123013748427},\n\t{11866668373555425458, 92730153767185534},\n\t{5609963430089506015, 115912692208981918},\n\t{17341285199088104971, 72445432630613698},\n\t{12453234462005355406, 90556790788267123},\n\t{10954857059079306353, 113195988485333904},\n\t{13693571323849132942, 141494985606667380},\n\t{17781854114260483896, 88434366004167112},\n\t{3780573569116053255, 110542957505208891},\n\t{114030942967678664, 138178696881511114},\n\t{4682955357782187069, 86361685550944446},\n\t{15077066234082509644, 107952106938680557},\n\t{5011274737320973344, 134940133673350697},\n\t{14661261756894078100, 84337583545844185},\n\t{4491519140835433913, 105421979432305232},\n\t{5614398926044292391, 131777474290381540},\n\t{12732371365632458552, 82360921431488462},\n\t{6692092170185797382, 102951151789360578},\n\t{17588487249587022536, 128688939736700722},\n\t{15604490549419276989, 80430587335437951},\n\t{14893927168346708332, 100538234169297439},\n\t{14005722942005997511, 125672792711621799},\n\t{15671105866394830300, 78545495444763624},\n\t{1142138259283986260, 98181869305954531},\n\t{15262730879387146537, 122727336632443163},\n\t{7233363790403272633, 76704585395276977},\n\t{13653390756431478696, 95880731744096221},\n\t{3231680390257184658, 119850914680120277},\n\t{4325643253124434363, 74906821675075173},\n\t{10018740084832930858, 93633527093843966},\n\t{3300053069186387764, 117041908867304958},\n\t{15897591223523656064, 73151193042065598},\n\t{10648616992549794273, 91438991302581998},\n\t{4087399203832467033, 114298739128227498},\n\t{14332621041645359599, 142873423910284372},\n\t{18181260187883125557, 89295889943927732},\n\t{4279831161144355331, 111619862429909666},\n\t{14573160988285219972, 139524828037387082},\n\t{13719911636105650386, 87203017523366926},\n\t{7926517508277287175, 109003771904208658},\n\t{684774848491833161, 136254714880260823},\n\t{7345513307948477581, 85159196800163014},\n\t{18405263671790372785, 106448996000203767},\n\t{18394893571310578077, 133061245000254709},\n\t{13802651491282805250, 83163278125159193},\n\t{3418256308821342851, 103954097656448992},\n\t{4272820386026678563, 129942622070561240},\n\t{2670512741266674102, 81214138794100775},\n\t{17173198981865506339, 101517673492625968},\n\t{3019754653622331308, 126897091865782461},\n\t{4193189667727651020, 79310682416114038},\n\t{14464859121514339583, 99138353020142547},\n\t{13469387883465536574, 123922941275178184},\n\t{8418367427165960359, 77451838296986365},\n\t{15134645302384838353, 96814797871232956},\n\t{471562554271496325, 121018497339041196},\n\t{9518098633274461011, 75636560836900747},\n\t{7285937273165688360, 94545701046125934},\n\t{18330793628311886258, 118182126307657417},\n\t{4539216990053847055, 73863828942285886},\n\t{14897393274422084627, 92329786177857357},\n\t{4786683537745442072, 115412232722321697},\n\t{14520892257159371055, 72132645451451060},\n\t{18151115321449213818, 90165806814313825},\n\t{8853836096529353561, 112707258517892282},\n\t{1843923083806916143, 140884073147365353},\n\t{12681666973447792349, 88052545717103345},\n\t{2017025661527576725, 110065682146379182},\n\t{11744654113764246714, 137582102682973977},\n\t{422879793461572340, 85988814176858736},\n\t{528599741826965425, 107486017721073420},\n\t{660749677283706782, 134357522151341775},\n\t{7330497575943398595, 83973451344588609},\n\t{13774807988356636147, 104966814180735761},\n\t{3383451930163631472, 131208517725919702},\n\t{15949715511634433382, 82005323578699813},\n\t{6102086334260878016, 102506654473374767},\n\t{3015921899398709616, 128133318091718459},\n\t{18025852251620051174, 80083323807324036},\n\t{4085571240815512351, 100104154759155046},\n\t{14330336087874166247, 125130193448943807},\n\t{15873989082562435760, 78206370905589879},\n\t{15230800334775656796, 97757963631987349},\n\t{5203442363187407284, 122197454539984187},\n\t{946308467778435600, 76373409087490117},\n\t{5794571603150432404, 95466761359362646},\n\t{16466586540792816313, 119333451699203307},\n\t{7985773578781816244, 74583407312002067},\n\t{5370530955049882401, 93229259140002584},\n\t{6713163693812353001, 116536573925003230},\n\t{18030785363914884337, 72835358703127018},\n\t{13315109668038829614, 91044198378908773},\n\t{2808829029766373305, 113805247973635967},\n\t{17346094342490130344, 142256559967044958},\n\t{6229622945628943561, 88910349979403099},\n\t{3175342663608791547, 111137937474253874},\n\t{13192550366365765242, 138922421842817342},\n\t{3633657960551215372, 86826513651760839},\n\t{18377130505971182927, 108533142064701048},\n\t{4524669058754427043, 135666427580876311},\n\t{9745447189362598758, 84791517238047694},\n\t{2958436949848472639, 105989396547559618},\n\t{12921418224165366607, 132486745684449522},\n\t{12687572408530742033, 82804216052780951},\n\t{11247779492236039638, 103505270065976189},\n\t{224666310012885835, 129381587582470237},\n\t{2446259452971747599, 80863492239043898},\n\t{12281196353069460307, 101079365298804872},\n\t{15351495441336825384, 126349206623506090},\n\t{14206370669262903769, 78968254139691306},\n\t{8534591299723853903, 98710317674614133},\n\t{15279925143082205283, 123387897093267666},\n\t{14161639232853766206, 77117435683292291},\n\t{13090363022639819853, 96396794604115364},\n\t{16362953778299774816, 120495993255144205},\n\t{12532689120651053212, 75309995784465128},\n\t{15665861400813816515, 94137494730581410},\n\t{10358954714162494836, 117671868413226763},\n\t{4168503687137865320, 73544917758266727},\n\t{598943590494943747, 91931147197833409},\n\t{5360365506546067587, 114913933997291761},\n\t{11312142901609972388, 143642417496614701},\n\t{9375932322719926695, 89776510935384188},\n\t{11719915403399908368, 112220638669230235},\n\t{10038208235822497557, 140275798336537794},\n\t{10885566165816448877, 87672373960336121},\n\t{18218643725697949000, 109590467450420151},\n\t{18161618638695048346, 136988084313025189},\n\t{13656854658398099168, 85617552695640743},\n\t{12459382304570236056, 107021940869550929},\n\t{1739169825430631358, 133777426086938662},\n\t{14922039196176308311, 83610891304336663},\n\t{14040862976792997485, 104513614130420829},\n\t{3716020665709083144, 130642017663026037},\n\t{4628355925281870917, 81651261039391273},\n\t{10397130925029726550, 102064076299239091},\n\t{8384727637859770284, 127580095374048864},\n\t{5240454773662356427, 79737559608780540},\n\t{6550568467077945534, 99671949510975675},\n\t{3576524565420044014, 124589936888719594},\n\t{6847013871814915412, 77868710555449746},\n\t{17782139376623420074, 97335888194312182},\n\t{13004302183924499284, 121669860242890228},\n\t{17351060901807587860, 76043662651806392},\n\t{3242082053549933210, 95054578314757991},\n\t{17887660622219580224, 118818222893447488},\n\t{11179787888887237640, 74261389308404680},\n\t{13974734861109047050, 92826736635505850},\n\t{8245046539531533005, 116033420794382313},\n\t{16682369133275677888, 72520887996488945},\n\t{7017903361312433648, 90651109995611182},\n\t{17995751238495317868, 113313887494513977},\n\t{8659630992836983623, 141642359368142472},\n\t{5412269370523114764, 88526474605089045},\n\t{11377022731581281359, 110658093256361306},\n\t{4997906377621825891, 138322616570451633},\n\t{14652906532082110942, 86451635356532270},\n\t{9092761128247862869, 108064544195665338},\n\t{2142579373455052779, 135080680244581673},\n\t{12868327154477877747, 84425425152863545},\n\t{2250350887815183471, 105531781441079432},\n\t{2812938609768979339, 131914726801349290},\n\t{6369772649532999991, 82446704250843306},\n\t{17185587848771025797, 103058380313554132},\n\t{3035240737254230630, 128822975391942666},\n\t{6508711479211282048, 80514359619964166},\n\t{17359261385868878368, 100642949524955207},\n\t{17087390713908710056, 125803686906194009},\n\t{3762090168551861929, 78627304316371256},\n\t{4702612710689827411, 98284130395464070},\n\t{15101637925217060072, 122855162994330087},\n\t{16356052730901744401, 76784476871456304},\n\t{1998321839917628885, 95980596089320381},\n\t{7109588318324424010, 119975745111650476},\n\t{13666864735807540814, 74984840694781547},\n\t{12471894901332038114, 93731050868476934},\n\t{6366496589810271835, 117163813585596168},\n\t{3979060368631419896, 73227383490997605},\n\t{9585511479216662775, 91534229363747006},\n\t{2758517312166052660, 114417786704683758},\n\t{12671518677062341634, 143022233380854697},\n\t{1002170145522881665, 89388895863034186},\n\t{10476084718758377889, 111736119828792732},\n\t{13095105898447972362, 139670149785990915},\n\t{5878598177316288774, 87293843616244322},\n\t{16571619758500136775, 109117304520305402},\n\t{11491152661270395161, 136396630650381753},\n\t{264441385652915120, 85247894156488596},\n\t{330551732066143900, 106559867695610745},\n\t{5024875683510067779, 133199834619513431},\n\t{10058076329834874218, 83249896637195894},\n\t{3349223375438816964, 104062370796494868},\n\t{4186529219298521205, 130077963495618585},\n\t{14145795808130045513, 81298727184761615},\n\t{13070558741735168987, 101623408980952019},\n\t{11726512408741573330, 127029261226190024},\n\t{7329070255463483331, 79393288266368765},\n\t{13773023837756742068, 99241610332960956},\n\t{17216279797195927585, 124052012916201195},\n\t{8454331864033760789, 77532508072625747},\n\t{5956228811614813082, 96915635090782184},\n\t{7445286014518516353, 121144543863477730},\n\t{9264989777501460624, 75715339914673581},\n\t{16192923240304213684, 94644174893341976},\n\t{1794409976670715490, 118305218616677471},\n\t{8039035263060279037, 73940761635423419},\n\t{5437108060397960892, 92425952044279274},\n\t{16019757112352226923, 115532440055349092},\n\t{788976158365366019, 72207775034593183},\n\t{14821278253238871236, 90259718793241478},\n\t{9303225779693813237, 112824648491551848},\n\t{11629032224617266546, 141030810614439810},\n\t{11879831158813179495, 88144256634024881},\n\t{1014730893234310657, 110180320792531102},\n\t{10491785653397664129, 137725400990663877},\n\t{8863209042587234033, 86078375619164923},\n\t{6467325284806654637, 107597969523956154},\n\t{17307528642863094104, 134497461904945192},\n\t{10817205401789433815, 84060913690590745},\n\t{18133192770664180173, 105076142113238431},\n\t{18054804944902837312, 131345177641548039},\n\t{18201782118205355176, 82090736025967524},\n\t{4305483574047142354, 102613420032459406},\n\t{14605226504413703751, 128266775040574257},\n\t{2210737537617482988, 80166734400358911},\n\t{16598479977304017447, 100208418000448638},\n\t{11524727934775246001, 125260522500560798},\n\t{2591268940807140847, 78287826562850499},\n\t{17074144231291089770, 97859783203563123},\n\t{16730994270686474309, 122324729004453904},\n\t{10456871419179046443, 76452955627783690},\n\t{3847717237119032246, 95566194534729613},\n\t{9421332564826178211, 119457743168412016},\n\t{5888332853016361382, 74661089480257510},\n\t{16583788103125227536, 93326361850321887},\n\t{16118049110479146516, 116657952312902359},\n\t{16991309721690548428, 72911220195563974},\n\t{12015765115258409727, 91139025244454968},\n\t{15019706394073012159, 113923781555568710},\n\t{9551260955736489391, 142404726944460888},\n\t{5969538097335305869, 89002954340288055},\n\t{2850236603241744433, 111253692925360069},\n}\n\nconst pow5InvNumBits64 = 122\n\nvar pow5InvSplit64 = [...]uint128{\n\t{1, 288230376151711744},\n\t{3689348814741910324, 230584300921369395},\n\t{2951479051793528259, 184467440737095516},\n\t{17118578500402463900, 147573952589676412},\n\t{12632330341676300947, 236118324143482260},\n\t{10105864273341040758, 188894659314785808},\n\t{15463389048156653253, 151115727451828646},\n\t{17362724847566824558, 241785163922925834},\n\t{17579528692795369969, 193428131138340667},\n\t{6684925324752475329, 154742504910672534},\n\t{18074578149087781173, 247588007857076054},\n\t{18149011334012135262, 198070406285660843},\n\t{3451162622983977240, 158456325028528675},\n\t{5521860196774363583, 253530120045645880},\n\t{4417488157419490867, 202824096036516704},\n\t{7223339340677503017, 162259276829213363},\n\t{7867994130342094503, 259614842926741381},\n\t{2605046489531765280, 207691874341393105},\n\t{2084037191625412224, 166153499473114484},\n\t{10713157136084480204, 265845599156983174},\n\t{12259874523609494487, 212676479325586539},\n\t{13497248433629505913, 170141183460469231},\n\t{14216899864323388813, 272225893536750770},\n\t{11373519891458711051, 217780714829400616},\n\t{5409467098425058518, 174224571863520493},\n\t{4965798542738183305, 278759314981632789},\n\t{7661987648932456967, 223007451985306231},\n\t{2440241304404055250, 178405961588244985},\n\t{3904386087046488400, 285449538541191976},\n\t{17880904128604832013, 228359630832953580},\n\t{14304723302883865611, 182687704666362864},\n\t{15133127457049002812, 146150163733090291},\n\t{16834306301794583852, 233840261972944466},\n\t{9778096226693756759, 187072209578355573},\n\t{15201174610838826053, 149657767662684458},\n\t{2185786488890659746, 239452428260295134},\n\t{5437978005854438120, 191561942608236107},\n\t{15418428848909281466, 153249554086588885},\n\t{6222742084545298729, 245199286538542217},\n\t{16046240111861969953, 196159429230833773},\n\t{1768945645263844993, 156927543384667019},\n\t{10209010661905972635, 251084069415467230},\n\t{8167208529524778108, 200867255532373784},\n\t{10223115638361732810, 160693804425899027},\n\t{1599589762411131202, 257110087081438444},\n\t{4969020624670815285, 205688069665150755},\n\t{3975216499736652228, 164550455732120604},\n\t{13739044029062464211, 263280729171392966},\n\t{7301886408508061046, 210624583337114373},\n\t{13220206756290269483, 168499666669691498},\n\t{17462981995322520850, 269599466671506397},\n\t{6591687966774196033, 215679573337205118},\n\t{12652048002903177473, 172543658669764094},\n\t{9175230360419352987, 276069853871622551},\n\t{3650835473593572067, 220855883097298041},\n\t{17678063637842498946, 176684706477838432},\n\t{13527506561580357021, 282695530364541492},\n\t{3443307619780464970, 226156424291633194},\n\t{6443994910566282300, 180925139433306555},\n\t{5155195928453025840, 144740111546645244},\n\t{15627011115008661990, 231584178474632390},\n\t{12501608892006929592, 185267342779705912},\n\t{2622589484121723027, 148213874223764730},\n\t{4196143174594756843, 237142198758023568},\n\t{10735612169159626121, 189713759006418854},\n\t{12277838550069611220, 151771007205135083},\n\t{15955192865369467629, 242833611528216133},\n\t{1696107848069843133, 194266889222572907},\n\t{12424932722681605476, 155413511378058325},\n\t{1433148282581017146, 248661618204893321},\n\t{15903913885032455010, 198929294563914656},\n\t{9033782293284053685, 159143435651131725},\n\t{14454051669254485895, 254629497041810760},\n\t{11563241335403588716, 203703597633448608},\n\t{16629290697806691620, 162962878106758886},\n\t{781423413297334329, 260740604970814219},\n\t{4314487545379777786, 208592483976651375},\n\t{3451590036303822229, 166873987181321100},\n\t{5522544058086115566, 266998379490113760},\n\t{4418035246468892453, 213598703592091008},\n\t{10913125826658934609, 170878962873672806},\n\t{10082303693170474728, 273406340597876490},\n\t{8065842954536379782, 218725072478301192},\n\t{17520720807854834795, 174980057982640953},\n\t{5897060404116273733, 279968092772225526},\n\t{1028299508551108663, 223974474217780421},\n\t{15580034865808528224, 179179579374224336},\n\t{17549358155809824511, 286687326998758938},\n\t{2971440080422128639, 229349861599007151},\n\t{17134547323305344204, 183479889279205720},\n\t{13707637858644275364, 146783911423364576},\n\t{14553522944347019935, 234854258277383322},\n\t{4264120725993795302, 187883406621906658},\n\t{10789994210278856888, 150306725297525326},\n\t{9885293106962350374, 240490760476040522},\n\t{529536856086059653, 192392608380832418},\n\t{7802327114352668369, 153914086704665934},\n\t{1415676938738538420, 246262538727465495},\n\t{1132541550990830736, 197010030981972396},\n\t{15663428499760305882, 157608024785577916},\n\t{17682787970132668764, 252172839656924666},\n\t{10456881561364224688, 201738271725539733},\n\t{15744202878575200397, 161390617380431786},\n\t{17812026976236499989, 258224987808690858},\n\t{3181575136763469022, 206579990246952687},\n\t{13613306553636506187, 165263992197562149},\n\t{10713244041592678929, 264422387516099439},\n\t{12259944048016053467, 211537910012879551},\n\t{6118606423670932450, 169230328010303641},\n\t{2411072648389671274, 270768524816485826},\n\t{16686253377679378312, 216614819853188660},\n\t{13349002702143502650, 173291855882550928},\n\t{17669055508687693916, 277266969412081485},\n\t{14135244406950155133, 221813575529665188},\n\t{240149081334393137, 177450860423732151},\n\t{11452284974360759988, 283921376677971441},\n\t{5472479164746697667, 227137101342377153},\n\t{11756680961281178780, 181709681073901722},\n\t{2026647139541122378, 145367744859121378},\n\t{18000030682233437097, 232588391774594204},\n\t{18089373360528660001, 186070713419675363},\n\t{3403452244197197031, 148856570735740291},\n\t{16513570034941246220, 238170513177184465},\n\t{13210856027952996976, 190536410541747572},\n\t{3189987192878576934, 152429128433398058},\n\t{1414630693863812771, 243886605493436893},\n\t{8510402184574870864, 195109284394749514},\n\t{10497670562401807014, 156087427515799611},\n\t{9417575270359070576, 249739884025279378},\n\t{14912757845771077107, 199791907220223502},\n\t{4551508647133041040, 159833525776178802},\n\t{10971762650154775986, 255733641241886083},\n\t{16156107749607641435, 204586912993508866},\n\t{9235537384944202825, 163669530394807093},\n\t{11087511001168814197, 261871248631691349},\n\t{12559357615676961681, 209496998905353079},\n\t{13736834907283479668, 167597599124282463},\n\t{18289587036911657145, 268156158598851941},\n\t{10942320814787415393, 214524926879081553},\n\t{16132554281313752961, 171619941503265242},\n\t{11054691591134363444, 274591906405224388},\n\t{16222450902391311402, 219673525124179510},\n\t{12977960721913049122, 175738820099343608},\n\t{17075388340318968271, 281182112158949773},\n\t{2592264228029443648, 224945689727159819},\n\t{5763160197165465241, 179956551781727855},\n\t{9221056315464744386, 287930482850764568},\n\t{14755542681855616155, 230344386280611654},\n\t{15493782960226403247, 184275509024489323},\n\t{1326979923955391628, 147420407219591459},\n\t{9501865507812447252, 235872651551346334},\n\t{11290841220991868125, 188698121241077067},\n\t{1653975347309673853, 150958496992861654},\n\t{10025058185179298811, 241533595188578646},\n\t{4330697733401528726, 193226876150862917},\n\t{14532604630946953951, 154581500920690333},\n\t{1116074521063664381, 247330401473104534},\n\t{4582208431592841828, 197864321178483627},\n\t{14733813189500004432, 158291456942786901},\n\t{16195403473716186445, 253266331108459042},\n\t{5577625149489128510, 202613064886767234},\n\t{8151448934333213131, 162090451909413787},\n\t{16731667109675051333, 259344723055062059},\n\t{17074682502481951390, 207475778444049647},\n\t{6281048372501740465, 165980622755239718},\n\t{6360328581260874421, 265568996408383549},\n\t{8777611679750609860, 212455197126706839},\n\t{10711438158542398211, 169964157701365471},\n\t{9759603424184016492, 271942652322184754},\n\t{11497031554089123517, 217554121857747803},\n\t{16576322872755119460, 174043297486198242},\n\t{11764721337440549842, 278469275977917188},\n\t{16790474699436260520, 222775420782333750},\n\t{13432379759549008416, 178220336625867000},\n\t{3045063541568861850, 285152538601387201},\n\t{17193446092222730773, 228122030881109760},\n\t{13754756873778184618, 182497624704887808},\n\t{18382503128506368341, 145998099763910246},\n\t{3586563302416817083, 233596959622256395},\n\t{2869250641933453667, 186877567697805116},\n\t{17052795772514404226, 149502054158244092},\n\t{12527077977055405469, 239203286653190548},\n\t{17400360011128145022, 191362629322552438},\n\t{2852241564676785048, 153090103458041951},\n\t{15631632947708587046, 244944165532867121},\n\t{8815957543424959314, 195955332426293697},\n\t{18120812478965698421, 156764265941034957},\n\t{14235904707377476180, 250822825505655932},\n\t{4010026136418160298, 200658260404524746},\n\t{17965416168102169531, 160526608323619796},\n\t{2919224165770098987, 256842573317791675},\n\t{2335379332616079190, 205474058654233340},\n\t{1868303466092863352, 164379246923386672},\n\t{6678634360490491686, 263006795077418675},\n\t{5342907488392393349, 210405436061934940},\n\t{4274325990713914679, 168324348849547952},\n\t{10528270399884173809, 269318958159276723},\n\t{15801313949391159694, 215455166527421378},\n\t{1573004715287196786, 172364133221937103},\n\t{17274202803427156150, 275782613155099364},\n\t{17508711057483635243, 220626090524079491},\n\t{10317620031244997871, 176500872419263593},\n\t{12818843235250086271, 282401395870821749},\n\t{13944423402941979340, 225921116696657399},\n\t{14844887537095493795, 180736893357325919},\n\t{15565258844418305359, 144589514685860735},\n\t{6457670077359736959, 231343223497377177},\n\t{16234182506113520537, 185074578797901741},\n\t{9297997190148906106, 148059663038321393},\n\t{11187446689496339446, 236895460861314229},\n\t{12639306166338981880, 189516368689051383},\n\t{17490142562555006151, 151613094951241106},\n\t{2158786396894637579, 242580951921985771},\n\t{16484424376483351356, 194064761537588616},\n\t{9498190686444770762, 155251809230070893},\n\t{11507756283569722895, 248402894768113429},\n\t{12895553841597688639, 198722315814490743},\n\t{17695140702761971558, 158977852651592594},\n\t{17244178680193423523, 254364564242548151},\n\t{10105994129412828495, 203491651394038521},\n\t{4395446488788352473, 162793321115230817},\n\t{10722063196803274280, 260469313784369307},\n\t{1198952927958798777, 208375451027495446},\n\t{15716557601334680315, 166700360821996356},\n\t{17767794532651667857, 266720577315194170},\n\t{14214235626121334286, 213376461852155336},\n\t{7682039686155157106, 170701169481724269},\n\t{1223217053622520399, 273121871170758831},\n\t{15735968901865657612, 218497496936607064},\n\t{16278123936234436413, 174797997549285651},\n\t{219556594781725998, 279676796078857043},\n\t{7554342905309201445, 223741436863085634},\n\t{9732823138989271479, 178993149490468507},\n\t{815121763415193074, 286389039184749612},\n\t{11720143854957885429, 229111231347799689},\n\t{13065463898708218666, 183288985078239751},\n\t{6763022304224664610, 146631188062591801},\n\t{3442138057275642729, 234609900900146882},\n\t{13821756890046245153, 187687920720117505},\n\t{11057405512036996122, 150150336576094004},\n\t{6623802375033462826, 240240538521750407},\n\t{16367088344252501231, 192192430817400325},\n\t{13093670675402000985, 153753944653920260},\n\t{2503129006933649959, 246006311446272417},\n\t{13070549649772650937, 196805049157017933},\n\t{17835137349301941396, 157444039325614346},\n\t{2710778055689733971, 251910462920982955},\n\t{2168622444551787177, 201528370336786364},\n\t{5424246770383340065, 161222696269429091},\n\t{1300097203129523457, 257956314031086546},\n\t{15797473021471260058, 206365051224869236},\n\t{8948629602435097724, 165092040979895389},\n\t{3249760919670425388, 264147265567832623},\n\t{9978506365220160957, 211317812454266098},\n\t{15361502721659949412, 169054249963412878},\n\t{2442311466204457120, 270486799941460606},\n\t{16711244431931206989, 216389439953168484},\n\t{17058344360286875914, 173111551962534787},\n\t{12535955717491360170, 276978483140055660},\n\t{10028764573993088136, 221582786512044528},\n\t{15401709288678291155, 177266229209635622},\n\t{9885339602917624555, 283625966735416996},\n\t{4218922867592189321, 226900773388333597},\n\t{14443184738299482427, 181520618710666877},\n\t{4175850161155765295, 145216494968533502},\n\t{10370709072591134795, 232346391949653603},\n\t{15675264887556728482, 185877113559722882},\n\t{5161514280561562140, 148701690847778306},\n\t{879725219414678777, 237922705356445290},\n\t{703780175531743021, 190338164285156232},\n\t{11631070584651125387, 152270531428124985},\n\t{162968861732249003, 243632850284999977},\n\t{11198421533611530172, 194906280227999981},\n\t{5269388412147313814, 155925024182399985},\n\t{8431021459435702103, 249480038691839976},\n\t{3055468352806651359, 199584030953471981},\n\t{17201769941212962380, 159667224762777584},\n\t{16454785461715008838, 255467559620444135},\n\t{13163828369372007071, 204374047696355308},\n\t{17909760324981426303, 163499238157084246},\n\t{2830174816776909822, 261598781051334795},\n\t{2264139853421527858, 209279024841067836},\n\t{16568707141704863579, 167423219872854268},\n\t{4373838538276319787, 267877151796566830},\n\t{3499070830621055830, 214301721437253464},\n\t{6488605479238754987, 171441377149802771},\n\t{3003071137298187333, 274306203439684434},\n\t{6091805724580460189, 219444962751747547},\n\t{15941491023890099121, 175555970201398037},\n\t{10748990379256517301, 280889552322236860},\n\t{8599192303405213841, 224711641857789488},\n\t{14258051472207991719, 179769313486231590},\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "json", + "path": "gno.land/p/demo/json", + "files": [ + { + "name": "LICENSE", + "body": "# MIT License\n\nCopyright (c) 2019 Pyzhov Stepan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + }, + { + "name": "README.md", + "body": "# JSON Parser\n\nThe JSON parser is a package that provides functionality for parsing and processing JSON strings. This package accepts JSON strings as byte slices.\n\nCurrently, gno does not [support the `reflect` package](https://docs.gno.land/concepts/effective-gno#reflection-is-never-clear), so it cannot retrieve type information at runtime. Therefore, it is designed to infer and handle type information when parsing JSON strings using a state machine approach.\n\nAfter passing through the state machine, JSON strings are represented as the `Node` type. The `Node` type represents nodes for JSON data, including various types such as `ObjectNode`, `ArrayNode`, `StringNode`, `NumberNode`, `BoolNode`, and `NullNode`.\n\nThis package provides methods for manipulating, searching, and extracting the Node type.\n\n## State Machine\n\nTo parse JSON strings, a [finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine) approach is used. The state machine transitions to the next state based on the current state and the input character while parsing the JSON string. Through this method, type information can be inferred and processed without reflect, and the amount of parser code can be significantly reduced.\n\nThe image below shows the state transitions of the state machine according to the states and input characters.\n\n```mermaid\nstateDiagram-v2\n [*] --\u003e __: Start\n __ --\u003e ST: String\n __ --\u003e MI: Number\n __ --\u003e ZE: Zero\n __ --\u003e IN: Integer\n __ --\u003e T1: Boolean (true)\n __ --\u003e F1: Boolean (false)\n __ --\u003e N1: Null\n __ --\u003e ec: Empty Object End\n __ --\u003e cc: Object End\n __ --\u003e bc: Array End\n __ --\u003e co: Object Begin\n __ --\u003e bo: Array Begin\n __ --\u003e cm: Comma\n __ --\u003e cl: Colon\n __ --\u003e OK: Success/End\n ST --\u003e OK: String Complete\n MI --\u003e OK: Number Complete\n ZE --\u003e OK: Zero Complete\n IN --\u003e OK: Integer Complete\n T1 --\u003e OK: True Complete\n F1 --\u003e OK: False Complete\n N1 --\u003e OK: Null Complete\n ec --\u003e OK: Empty Object Complete\n cc --\u003e OK: Object Complete\n bc --\u003e OK: Array Complete\n co --\u003e OB: Inside Object\n bo --\u003e AR: Inside Array\n cm --\u003e KE: Expecting New Key\n cm --\u003e VA: Expecting New Value\n cl --\u003e VA: Expecting Value\n OB --\u003e ST: String in Object (Key)\n OB --\u003e ec: Empty Object\n OB --\u003e cc: End Object\n AR --\u003e ST: String in Array\n AR --\u003e bc: End Array\n KE --\u003e ST: String as Key\n VA --\u003e ST: String as Value\n VA --\u003e MI: Number as Value\n VA --\u003e T1: True as Value\n VA --\u003e F1: False as Value\n VA --\u003e N1: Null as Value\n OK --\u003e [*]: End\n```\n\n## Examples\n\nThis package provides parsing functionality along with encoding and decoding functionality. The following examples demonstrate how to use this package.\n\n### Decoding\n\nDecoding (or Unmarshaling) is the functionality that converts an input byte slice JSON string into a `Node` type.\n\nThe converted `Node` type allows you to modify the JSON data or search and extract data that meets specific conditions.\n\n```go\npackage main\n\nimport (\n \"fmt\"\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node, err := json.Unmarshal([]byte(`{\"foo\": \"var\"}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"node: %v\", node)\n}\n```\n\n### Encoding\n\nEncoding (or Marshaling) is the functionality that converts JSON data represented as a Node type into a byte slice JSON string.\n\n\u003e ⚠️ Caution: Converting a large `Node` type into a JSON string may _impact performance_. or might be cause _unexpected behavior_.\n\n```go\npackage main\n\nimport (\n \"fmt\"\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node := ObjectNode(\"\", map[string]*Node{\n \"foo\": StringNode(\"foo\", \"bar\"),\n \"baz\": NumberNode(\"baz\", 100500),\n \"qux\": NullNode(\"qux\"),\n })\n\n b, err := json.Marshal(node)\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"json: %s\", string(b))\n}\n```\n\n### Searching\n\nOnce the JSON data converted into a `Node` type, you can **search** and **extract** data that satisfy specific conditions. For example, you can find data with a specific type or data with a specific key.\n\nTo use this functionality, you can use methods in the `GetXXX` prefixed methods. The `MustXXX` methods also provide the same functionality as the former methods, but they will **panic** if data doesn't satisfies the condition.\n\nHere is an example of finding data with a specific key. For more examples, please refer to the [node.gno](node.gno) file.\n\n```go\npackage main\n\nimport (\n \"fmt\"\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n root, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n value, err := root.GetKey(\"foo\")\n if err != nil {\n ufmt.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n if value.MustBool() != true {\n ufmt.Errorf(\"value is not true\")\n }\n\n value, err = root.GetKey(\"bar\")\n if err != nil {\n t.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n _, err = root.GetKey(\"baz\")\n if err == nil {\n t.Errorf(\"key baz is not exist. must be failed\")\n }\n}\n```\n\n## Contributing\n\nPlease submit any issues or pull requests for this package through the GitHub repository at [gnolang/gno](\u003chttps://github.com/gnolang/gno\u003e).\n" + }, + { + "name": "buffer.gno", + "body": "package json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype buffer struct {\n\tdata []byte\n\tlength int\n\tindex int\n\n\tlast States\n\tstate States\n\tclass Classes\n}\n\n// newBuffer creates a new buffer with the given data\nfunc newBuffer(data []byte) *buffer {\n\treturn \u0026buffer{\n\t\tdata: data,\n\t\tlength: len(data),\n\t\tlast: GO,\n\t\tstate: GO,\n\t}\n}\n\n// first retrieves the first non-whitespace (or other escaped) character in the buffer.\nfunc (b *buffer) first() (byte, error) {\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tc := b.data[b.index]\n\n\t\tif !(c == whiteSpace || c == carriageReturn || c == newLine || c == tab) {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\n\treturn 0, io.EOF\n}\n\n// current returns the byte of the current index.\nfunc (b *buffer) current() (byte, error) {\n\tif b.index \u003e= b.length {\n\t\treturn 0, io.EOF\n\t}\n\n\treturn b.data[b.index], nil\n}\n\n// next moves to the next byte and returns it.\nfunc (b *buffer) next() (byte, error) {\n\tb.index++\n\treturn b.current()\n}\n\n// step just moves to the next position.\nfunc (b *buffer) step() error {\n\t_, err := b.next()\n\treturn err\n}\n\n// move moves the index by the given position.\nfunc (b *buffer) move(pos int) error {\n\tnewIndex := b.index + pos\n\n\tif newIndex \u003e b.length {\n\t\treturn io.EOF\n\t}\n\n\tb.index = newIndex\n\n\treturn nil\n}\n\n// slice returns the slice from the current index to the given position.\nfunc (b *buffer) slice(pos int) ([]byte, error) {\n\tend := b.index + pos\n\n\tif end \u003e b.length {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn b.data[b.index:end], nil\n}\n\n// sliceFromIndices returns a slice of the buffer's data starting from 'start' up to (but not including) 'stop'.\nfunc (b *buffer) sliceFromIndices(start, stop int) []byte {\n\tif start \u003e b.length {\n\t\tstart = b.length\n\t}\n\n\tif stop \u003e b.length {\n\t\tstop = b.length\n\t}\n\n\treturn b.data[start:stop]\n}\n\n// skip moves the index to skip the given byte.\nfunc (b *buffer) skip(bs byte) error {\n\tfor b.index \u003c b.length {\n\t\tif b.data[b.index] == bs \u0026\u0026 !b.backslash() {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn io.EOF\n}\n\n// skipAny moves the index until it encounters one of the given set of bytes.\nfunc (b *buffer) skipAny(endTokens map[byte]bool) error {\n\tfor b.index \u003c b.length {\n\t\tif _, exists := endTokens[b.data[b.index]]; exists {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\t// build error message\n\tvar tokens []string\n\tfor token := range endTokens {\n\t\ttokens = append(tokens, string(token))\n\t}\n\n\treturn ufmt.Errorf(\n\t\t\"EOF reached before encountering one of the expected tokens: %s\",\n\t\tstrings.Join(tokens, \", \"),\n\t)\n}\n\n// skipAndReturnIndex moves the buffer index forward by one and returns the new index.\nfunc (b *buffer) skipAndReturnIndex() (int, error) {\n\terr := b.step()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn b.index, nil\n}\n\n// skipUntil moves the buffer index forward until it encounters a byte contained in the endTokens set.\nfunc (b *buffer) skipUntil(endTokens map[byte]bool) (int, error) {\n\tfor b.index \u003c b.length {\n\t\tcurrentByte, err := b.current()\n\t\tif err != nil {\n\t\t\treturn b.index, err\n\t\t}\n\n\t\t// Check if the current byte is in the set of end tokens.\n\t\tif _, exists := endTokens[currentByte]; exists {\n\t\t\treturn b.index, nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn b.index, io.EOF\n}\n\n// significantTokens is a map where the keys are the significant characters in a JSON path.\n// The values in the map are all true, which allows us to use the map as a set for quick lookups.\nvar significantTokens = map[byte]bool{\n\tdot: true, // access properties of an object\n\tdollarSign: true, // root object\n\tatSign: true, // current object\n\tbracketOpen: true, // start of an array index or filter expression\n\tbracketClose: true, // end of an array index or filter expression\n}\n\n// filterTokens stores the filter expression tokens.\nvar filterTokens = map[byte]bool{\n\taesterisk: true, // wildcard\n\tandSign: true,\n\torSign: true,\n}\n\n// skipToNextSignificantToken advances the buffer index to the next significant character.\n// Significant characters are defined based on the JSON path syntax.\nfunc (b *buffer) skipToNextSignificantToken() {\n\tfor b.index \u003c b.length {\n\t\tcurrent := b.data[b.index]\n\n\t\tif _, ok := significantTokens[current]; ok {\n\t\t\tbreak\n\t\t}\n\n\t\tb.index++\n\t}\n}\n\n// backslash checks to see if the number of backslashes before the current index is odd.\n//\n// This is used to check if the current character is escaped. However, unlike the \"unescape\" function,\n// \"backslash\" only serves to check the number of backslashes.\nfunc (b *buffer) backslash() bool {\n\tif b.index == 0 {\n\t\treturn false\n\t}\n\n\tcount := 0\n\tfor i := b.index - 1; ; i-- {\n\t\tif i \u003e= b.length || b.data[i] != backSlash {\n\t\t\tbreak\n\t\t}\n\n\t\tcount++\n\n\t\tif i == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn count%2 != 0\n}\n\n// numIndex holds a map of valid numeric characters\nvar numIndex = map[byte]bool{\n\t'0': true,\n\t'1': true,\n\t'2': true,\n\t'3': true,\n\t'4': true,\n\t'5': true,\n\t'6': true,\n\t'7': true,\n\t'8': true,\n\t'9': true,\n\t'.': true,\n\t'e': true,\n\t'E': true,\n}\n\n// pathToken checks if the current token is a valid JSON path token.\nfunc (b *buffer) pathToken() error {\n\tvar stack []byte\n\n\tinToken := false\n\tinNumber := false\n\tfirst := b.index\n\n\tfor b.index \u003c b.length {\n\t\tc := b.data[b.index]\n\n\t\tswitch {\n\t\tcase c == doubleQuote || c == singleQuote:\n\t\t\tinToken = true\n\t\t\tif err := b.step(); err != nil {\n\t\t\t\treturn errors.New(\"error stepping through buffer\")\n\t\t\t}\n\n\t\t\tif err := b.skip(c); err != nil {\n\t\t\t\treturn errors.New(\"unmatched quote in path\")\n\t\t\t}\n\n\t\t\tif b.index \u003e= b.length {\n\t\t\t\treturn errors.New(\"unmatched quote in path\")\n\t\t\t}\n\n\t\tcase c == bracketOpen || c == parenOpen:\n\t\t\tinToken = true\n\t\t\tstack = append(stack, c)\n\n\t\tcase c == bracketClose || c == parenClose:\n\t\t\tinToken = true\n\t\t\tif len(stack) == 0 || (c == bracketClose \u0026\u0026 stack[len(stack)-1] != bracketOpen) || (c == parenClose \u0026\u0026 stack[len(stack)-1] != parenOpen) {\n\t\t\t\treturn errors.New(\"mismatched bracket or parenthesis\")\n\t\t\t}\n\n\t\t\tstack = stack[:len(stack)-1]\n\n\t\tcase pathStateContainsValidPathToken(c):\n\t\t\tinToken = true\n\n\t\tcase c == plus || c == minus:\n\t\t\tif inNumber || (b.index \u003e 0 \u0026\u0026 numIndex[b.data[b.index-1]]) {\n\t\t\t\tinToken = true\n\t\t\t} else if !inToken \u0026\u0026 (b.index+1 \u003c b.length \u0026\u0026 numIndex[b.data[b.index+1]]) {\n\t\t\t\tinToken = true\n\t\t\t\tinNumber = true\n\t\t\t} else if !inToken {\n\t\t\t\treturn errors.New(\"unexpected operator at start of token\")\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif len(stack) != 0 || inToken {\n\t\t\t\tinToken = true\n\t\t\t} else {\n\t\t\t\tgoto end\n\t\t\t}\n\t\t}\n\n\t\tb.index++\n\t}\n\nend:\n\tif len(stack) != 0 {\n\t\treturn errors.New(\"unclosed bracket or parenthesis at end of path\")\n\t}\n\n\tif first == b.index {\n\t\treturn errors.New(\"no token found\")\n\t}\n\n\tif inNumber \u0026\u0026 !numIndex[b.data[b.index-1]] {\n\t\tinNumber = false\n\t}\n\n\treturn nil\n}\n\nfunc pathStateContainsValidPathToken(c byte) bool {\n\tif _, ok := significantTokens[c]; ok {\n\t\treturn true\n\t}\n\n\tif _, ok := filterTokens[c]; ok {\n\t\treturn true\n\t}\n\n\tif _, ok := numIndex[c]; ok {\n\t\treturn true\n\t}\n\n\tif 'A' \u003c= c \u0026\u0026 c \u003c= 'Z' || 'a' \u003c= c \u0026\u0026 c \u003c= 'z' {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (b *buffer) numeric(token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(doubleQuote)\n\t\tif b.class == __ {\n\t\t\treturn errors.New(\"invalid token found while parsing path\")\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\tif token {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn errors.New(\"invalid token found while parsing path\")\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\treturn nil\n\t\t}\n\n\t\tif b.state \u003c MI || b.state \u003e E3 {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\tif b.last != ZE \u0026\u0026 b.last != IN \u0026\u0026 b.last != FR \u0026\u0026 b.last != E3 {\n\t\treturn errors.New(\"invalid token found while parsing path\")\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) getClasses(c byte) Classes {\n\tif b.data[b.index] \u003e= 128 {\n\t\treturn C_ETC\n\t}\n\n\tif c == singleQuote {\n\t\treturn QuoteAsciiClasses[b.data[b.index]]\n\t}\n\n\treturn AsciiClasses[b.data[b.index]]\n}\n\nfunc (b *buffer) getState() States {\n\tb.last = b.state\n\n\tb.class = b.getClasses(doubleQuote)\n\tif b.class == __ {\n\t\treturn __\n\t}\n\n\tb.state = StateTransitionTable[b.last][b.class]\n\n\treturn b.state\n}\n\n// string parses a string token from the buffer.\nfunc (b *buffer) string(search byte, token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(search)\n\n\t\tif b.class == __ {\n\t\t\treturn errors.New(\"invalid token found while parsing path\")\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\treturn errors.New(\"invalid token found while parsing path\")\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\tbreak\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) word(bs []byte) error {\n\tvar c byte\n\n\tmax := len(bs)\n\tindex := 0\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tc = b.data[b.index]\n\n\t\tif c != bs[index] {\n\t\t\treturn errors.New(\"invalid token found while parsing path\")\n\t\t}\n\n\t\tindex++\n\t\tif index \u003e= max {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != max {\n\t\treturn errors.New(\"invalid token found while parsing path\")\n\t}\n\n\treturn nil\n}\n\nfunc numberKind2f64(value interface{}) (result float64, err error) {\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\tresult = typed\n\tcase float32:\n\t\tresult = float64(typed)\n\tcase int:\n\t\tresult = float64(typed)\n\tcase int8:\n\t\tresult = float64(typed)\n\tcase int16:\n\t\tresult = float64(typed)\n\tcase int32:\n\t\tresult = float64(typed)\n\tcase int64:\n\t\tresult = float64(typed)\n\tcase uint:\n\t\tresult = float64(typed)\n\tcase uint8:\n\t\tresult = float64(typed)\n\tcase uint16:\n\t\tresult = float64(typed)\n\tcase uint32:\n\t\tresult = float64(typed)\n\tcase uint64:\n\t\tresult = float64(typed)\n\tdefault:\n\t\terr = ufmt.Errorf(\"invalid number type: %T\", value)\n\t}\n\n\treturn\n}\n" + }, + { + "name": "buffer_test.gno", + "body": "package json\n\nimport \"testing\"\n\nfunc TestBufferCurrent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\texpected byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid current byte\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 1,\n\t\t\t},\n\t\t\texpected: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 4,\n\t\t\t},\n\t\t\texpected: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.current()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.current() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"buffer.current() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferStep(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid step\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.step()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.step() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferNext(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twant byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid next byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twant: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twant: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.next()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.next() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"buffer.next() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSlice(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twant []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid slice -- 0 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 0,\n\t\t\twant: nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 1 character\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 1,\n\t\t\twant: []byte(\"t\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 2 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twant: []byte(\"es\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 3 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 3,\n\t\t\twant: []byte(\"tes\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 4 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 4,\n\t\t\twant: []byte(\"test\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\tpos: 2,\n\t\t\twant: nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.slice(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.slice() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif string(got) != string(tt.want) {\n\t\t\t\tt.Errorf(\"buffer.slice() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferMove(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twantErr bool\n\t\twantIdx int\n\t}{\n\t\t{\n\t\t\tname: \"Valid move\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twantErr: false,\n\t\t\twantIdx: 3,\n\t\t},\n\t\t{\n\t\t\tname: \"Move beyond length\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 4,\n\t\t\twantErr: true,\n\t\t\twantIdx: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.move(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.move() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tif tt.buffer.index != tt.wantIdx {\n\t\t\t\tt.Errorf(\"buffer.move() index = %v, want %v\", tt.buffer.index, tt.wantIdx)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSkip(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tb byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Skip byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Skip to EOF\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'x',\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.skip(tt.b)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.skip() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSkipAny(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\ts map[byte]bool\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Skip any valid byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\ts: map[byte]bool{'e': true, 'o': true},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Skip any to EOF\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\ts: map[byte]bool{'x': true, 'y': true},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.skipAny(tt.s)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.skipAny() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipToNextSignificantToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected int\n\t}{\n\t\t{\"No significant chars\", []byte(\"abc\"), 3},\n\t\t{\"One significant char at start\", []byte(\".abc\"), 0},\n\t\t{\"Significant char in middle\", []byte(\"ab.c\"), 2},\n\t\t{\"Multiple significant chars\", []byte(\"a$.c\"), 1},\n\t\t{\"Significant char at end\", []byte(\"abc$\"), 3},\n\t\t{\"Only significant chars\", []byte(\"$.\"), 0},\n\t\t{\"Empty string\", []byte(\"\"), 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.input)\n\t\t\tb.skipToNextSignificantToken()\n\t\t\tif b.index != tt.expected {\n\t\t\t\tt.Errorf(\"after skipToNextSignificantToken(), got index = %v, want %v\", b.index, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc mockBuffer(s string) *buffer {\n\treturn newBuffer([]byte(s))\n}\n\nfunc TestSkipAndReturnIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"StartOfString\", \"\", 0},\n\t\t{\"MiddleOfString\", \"abcdef\", 1},\n\t\t{\"EndOfString\", \"abc\", 1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipAndReturnIndex()\n\t\t\tif err != nil \u0026\u0026 tt.input != \"\" { // Expect no error unless input is empty\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipUntil(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\ttokens map[byte]bool\n\t\texpected int\n\t}{\n\t\t{\"SkipToToken\", \"abcdefg\", map[byte]bool{'c': true}, 2},\n\t\t{\"SkipToEnd\", \"abcdefg\", map[byte]bool{'h': true}, 7},\n\t\t{\"SkipNone\", \"abcdefg\", map[byte]bool{'a': true}, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipUntil(tt.tokens)\n\t\t\tif err != nil \u0026\u0026 got != len(tt.input) { // Expect error only if reached end without finding token\n\t\t\t\tt.Errorf(\"skipUntil() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipUntil() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSliceFromIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\tstart int\n\t\tend int\n\t\texpected string\n\t}{\n\t\t{\"FullString\", \"abcdefg\", 0, 7, \"abcdefg\"},\n\t\t{\"Substring\", \"abcdefg\", 2, 5, \"cde\"},\n\t\t{\"OutOfBounds\", \"abcdefg\", 5, 10, \"fg\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot := buf.sliceFromIndices(tt.start, tt.end)\n\t\t\tif string(got) != tt.expected {\n\t\t\t\tt.Errorf(\"sliceFromIndices() = %v, want %v\", string(got), tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\tindex int\n\t\tisErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Simple valid path\",\n\t\t\tpath: \"@.length\",\n\t\t\tindex: 8,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr\",\n\t\t\tpath: \"@['foo'].0.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr and simple fomula\",\n\t\t\tpath: \"@['foo'].[(@.length - 1)].*\",\n\t\t\tindex: 27,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with filter expr\",\n\t\t\tpath: \"@['foo'].[?(@.bar == 1 \u0026 @.baz \u003c @.length)].*\",\n\t\t\tindex: 45,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"addition of foo and bar\",\n\t\t\tpath: \"@.foo+@.bar\",\n\t\t\tindex: 11,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical AND of foo and bar\",\n\t\t\tpath: \"@.foo \u0026\u0026 @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical OR of foo and bar\",\n\t\t\tpath: \"@.foo || @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing third element of foo\",\n\t\t\tpath: \"@.foo,3\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing last element of array\",\n\t\t\tpath: \"@.length-1\",\n\t\t\tindex: 10,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"number 1\",\n\t\t\tpath: \"1\",\n\t\t\tindex: 1,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float\",\n\t\t\tpath: \"3.1e4\",\n\t\t\tindex: 5,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with minus\",\n\t\t\tpath: \"3.1e-4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with plus\",\n\t\t\tpath: \"3.1e+4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative number\",\n\t\t\tpath: \"-12345\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float\",\n\t\t\tpath: \"-3.1e4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with minus\",\n\t\t\tpath: \"-3.1e-4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with plus\",\n\t\t\tpath: \"-3.1e+4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string number\",\n\t\t\tpath: \"'12345'\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with backslash\",\n\t\t\tpath: \"'foo \\\\'bar '\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with inner double quotes\",\n\t\t\tpath: \"'foo \\\"bar \\\"'\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 1\",\n\t\t\tpath: \"(@abc)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 2\",\n\t\t\tpath: \"[()]\",\n\t\t\tindex: 4,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch\",\n\t\t\tpath: \"[(])\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 2\",\n\t\t\tpath: \"(\",\n\t\t\tindex: 1,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 3\",\n\t\t\tpath: \"())]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch\",\n\t\t\tpath: \"[()\",\n\t\t\tindex: 3,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch 2\",\n\t\t\tpath: \"()]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"path does not close bracket\",\n\t\t\tpath: \"@.foo[)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := newBuffer([]byte(tt.path))\n\n\t\t\terr := buf.pathToken()\n\t\t\tif tt.isErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err == nil \u0026\u0026 tt.isErr {\n\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t}\n\n\t\t\tif buf.index != tt.index {\n\t\t\t\tt.Errorf(\"Expected final index %d, got %d (token: `%s`) for path `%s`\", tt.index, buf.index, string(buf.data[buf.index]), tt.path)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferFirst(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\texpected byte\n\t}{\n\t\t{\n\t\t\tname: \"Valid first byte\",\n\t\t\tdata: []byte(\"test\"),\n\t\t\texpected: 't',\n\t\t},\n\t\t{\n\t\t\tname: \"Empty buffer\",\n\t\t\tdata: []byte(\"\"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"Whitespace buffer\",\n\t\t\tdata: []byte(\" \"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"whitespace in middle\",\n\t\t\tdata: []byte(\"hello world\"),\n\t\t\texpected: 'h',\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.data)\n\n\t\t\tgot, err := b.first()\n\t\t\tif err != nil \u0026\u0026 tt.expected != 0 {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"Expected first byte to be %q, got %q\", tt.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "decode.gno", + "body": "// ref: https://github.com/spyzhov/ajson/blob/master/decode.go\n\npackage json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// This limits the max nesting depth to prevent stack overflow.\n// This is permitted by https://tools.ietf.org/html/rfc7159#section-9\nconst maxNestingDepth = 10000\n\n// Unmarshal parses the JSON-encoded data and returns a Node.\n// The data must be a valid JSON-encoded value.\n//\n// Usage:\n//\n//\tnode, err := json.Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err != nil {\n//\t\tufmt.Println(err)\n//\t}\n//\tprintln(node) // {\"key\": \"value\"}\nfunc Unmarshal(data []byte) (*Node, error) {\n\tbuf := newBuffer(data)\n\n\tvar (\n\t\tstate States\n\t\tkey *string\n\t\tcurrent *Node\n\t\tnesting int\n\t\tuseKey = func() **string {\n\t\t\ttmp := cptrs(key)\n\t\t\tkey = nil\n\t\t\treturn \u0026tmp\n\t\t}\n\t\terr error\n\t)\n\n\tif _, err = buf.first(); err != nil {\n\t\treturn nil, io.EOF\n\t}\n\n\tfor {\n\t\tstate = buf.getState()\n\t\tif state == __ {\n\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t}\n\n\t\t// region state machine\n\t\tif state \u003e= GO {\n\t\t\tswitch buf.state {\n\t\t\tcase ST: // string\n\t\t\t\tif current != nil \u0026\u0026 current.IsObject() \u0026\u0026 key == nil {\n\t\t\t\t\t// key detected\n\t\t\t\t\tif key, err = getString(buf); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tbuf.state = CO\n\t\t\t\t} else {\n\t\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, String, nesting, useKey())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\terr = buf.string(doubleQuote, false)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\t\t\t\t\tbuf.state = OK\n\t\t\t\t}\n\n\t\t\tcase MI, ZE, IN: // number\n\t\t\t\tcurrent, err = processNumericNode(current, buf, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase T1, F1: // boolean\n\t\t\t\tliteral := falseLiteral\n\t\t\t\tif buf.state == T1 {\n\t\t\t\t\tliteral = trueLiteral\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Boolean, literal, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase N1: // null\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Null, nullLiteral, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// region action\n\t\t\tswitch state {\n\t\t\tcase ec, cc: // \u003cempty\u003e }\n\t\t\t\tif key != nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Object)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase bc: // ]\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Array)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase co, bo: // { [\n\t\t\t\tvalTyp, bState := Object, OB\n\t\t\t\tif state == bo {\n\t\t\t\t\tvalTyp, bState = Array, AR\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, valTyp, nesting, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.state = bState\n\n\t\t\tcase cm: // ,\n\t\t\t\tif current == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif !current.isContainer() {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif current.IsObject() {\n\t\t\t\t\tbuf.state = KE // key expected\n\t\t\t\t} else {\n\t\t\t\t\tbuf.state = VA // value expected\n\t\t\t\t}\n\n\t\t\tcase cl: // :\n\t\t\t\tif current == nil || !current.IsObject() || key == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tbuf.state = VA\n\n\t\t\tdefault:\n\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t}\n\t\t}\n\n\t\tif buf.step() != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif _, err = buf.first(); err != nil {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif current == nil || buf.state != OK {\n\t\treturn nil, io.EOF\n\t}\n\n\troot := current.root()\n\tif !root.ready() {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn root, err\n}\n\n// UnmarshalSafe parses the JSON-encoded data and returns a Node.\nfunc UnmarshalSafe(data []byte) (*Node, error) {\n\tvar safe []byte\n\tsafe = append(safe, data...)\n\treturn Unmarshal(safe)\n}\n\n// processNumericNode creates a new node, processes a numeric value,\n// sets the node's borders, and moves to the previous node.\nfunc processNumericNode(current *Node, buf *buffer, key **string) (*Node, error) {\n\tvar err error\n\tcurrent, err = createNode(current, buf, Number, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = buf.numeric(false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcurrent.borders[1] = buf.index\n\tif current.prev != nil {\n\t\tcurrent = current.prev\n\t}\n\n\tbuf.index -= 1\n\tbuf.state = OK\n\n\treturn current, nil\n}\n\n// processLiteralNode creates a new node, processes a literal value,\n// sets the node's borders, and moves to the previous node.\nfunc processLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteralValue []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tcurrent, nesting, err = createLiteralNode(current, buf, literalType, literalValue, useKey, nesting)\n\tif err != nil {\n\t\treturn nil, nesting, err\n\t}\n\treturn current, nesting, nil\n}\n\n// isValidContainerType checks if the current node is a valid container (object or array).\n// The container must satisfy the following conditions:\n// 1. The current node must not be nil.\n// 2. The current node must be an object or array.\n// 3. The current node must not be ready.\nfunc isValidContainerType(current *Node, nodeType ValueType) bool {\n\tswitch nodeType {\n\tcase Object:\n\t\treturn current != nil \u0026\u0026 current.IsObject() \u0026\u0026 !current.ready()\n\tcase Array:\n\t\treturn current != nil \u0026\u0026 current.IsArray() \u0026\u0026 !current.ready()\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// getString extracts a string from the buffer and advances the buffer index past the string.\nfunc getString(b *buffer) (*string, error) {\n\tstart := b.index\n\tif err := b.string(doubleQuote, false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalue, ok := Unquote(b.data[start:b.index+1], doubleQuote)\n\tif !ok {\n\t\treturn nil, unexpectedTokenError(b.data, start)\n\t}\n\n\treturn \u0026value, nil\n}\n\n// createNode creates a new node and sets the key if it is not nil.\nfunc createNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tkey **string,\n) (*Node, error) {\n\tvar err error\n\tcurrent, err = NewNode(current, buf, nodeType, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn current, nil\n}\n\n// createNestedNode creates a new nested node (array or object) and sets the key if it is not nil.\nfunc createNestedNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tnesting int,\n\tkey **string,\n) (*Node, int, error) {\n\tvar err error\n\tif nesting, err = checkNestingDepth(nesting); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\tif current, err = createNode(current, buf, nodeType, key); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\treturn current, nesting, nil\n}\n\n// createLiteralNode creates a new literal node and sets the key if it is not nil.\n// The literal is a byte slice that represents a boolean or null value.\nfunc createLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteral []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tif current, err = createNode(current, buf, literalType, useKey); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif err = buf.word(literal); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, false)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// updateNode updates the current node and returns the previous node.\nfunc updateNode(\n\tcurrent *Node, buf *buffer, nesting int, decreaseLevel bool,\n) (*Node, int) {\n\tcurrent.borders[1] = buf.index + 1\n\n\tprev := current.prev\n\tif prev == nil {\n\t\treturn current, nesting\n\t}\n\n\tcurrent = prev\n\tif decreaseLevel {\n\t\tnesting--\n\t}\n\n\treturn current, nesting\n}\n\n// updateNodeAndSetBufferState updates the current node and sets the buffer state to OK.\nfunc updateNodeAndSetBufferState(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnesting int,\n\ttyp ValueType,\n) (*Node, int, error) {\n\tif !isValidContainerType(current, typ) {\n\t\treturn nil, nesting, unexpectedTokenError(buf.data, buf.index)\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// checkNestingDepth checks if the nesting depth is within the maximum allowed depth.\nfunc checkNestingDepth(nesting int) (int, error) {\n\tif nesting \u003e= maxNestingDepth {\n\t\treturn nesting, errors.New(\"maximum nesting depth exceeded\")\n\t}\n\n\treturn nesting + 1, nil\n}\n\nfunc unexpectedTokenError(data []byte, index int) error {\n\treturn ufmt.Errorf(\"unexpected token at index %d. data %b\", index, data)\n}\n" + }, + { + "name": "decode_test.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\ntype testNode struct {\n\tname string\n\tinput []byte\n\t_type ValueType\n\tvalue []byte\n}\n\nfunc simpleValid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.input, err.Error())\n\t} else if root == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t} else if root.nodeType != test._type {\n\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t} else if !bytes.Equal(root.source(), test.value) {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t}\n}\n\nfunc simpleInvalid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): error expected, got '%s'\", test.name, root.source())\n\t} else if root != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is not nil\", test.name)\n\t}\n}\n\nfunc simpleCorrupted(name string) *testNode {\n\treturn \u0026testNode{name: name, input: []byte(name)}\n}\n\nfunc TestUnmarshal_StringSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"blank\", input: []byte(\"\\\"\\\"\"), _type: String, value: []byte(\"\\\"\\\"\")},\n\t\t{name: \"char\", input: []byte(\"\\\"c\\\"\"), _type: String, value: []byte(\"\\\"c\\\"\")},\n\t\t{name: \"word\", input: []byte(\"\\\"cat\\\"\"), _type: String, value: []byte(\"\\\"cat\\\"\")},\n\t\t{name: \"spaces\", input: []byte(\" \\\"good cat or dog\\\"\\r\\n \"), _type: String, value: []byte(\"\\\"good cat or dog\\\"\")},\n\t\t{name: \"backslash\", input: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\")},\n\t\t{name: \"backslash 2\", input: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NumericSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"1\", input: []byte(\"1\"), _type: Number, value: []byte(\"1\")},\n\t\t{name: \"-1\", input: []byte(\"-1\"), _type: Number, value: []byte(\"-1\")},\n\n\t\t{name: \"1234567890\", input: []byte(\"1234567890\"), _type: Number, value: []byte(\"1234567890\")},\n\t\t{name: \"-123\", input: []byte(\"-123\"), _type: Number, value: []byte(\"-123\")},\n\n\t\t{name: \"123.456\", input: []byte(\"123.456\"), _type: Number, value: []byte(\"123.456\")},\n\t\t{name: \"-123.456\", input: []byte(\"-123.456\"), _type: Number, value: []byte(\"-123.456\")},\n\n\t\t{name: \"1e3\", input: []byte(\"1e3\"), _type: Number, value: []byte(\"1e3\")},\n\t\t{name: \"1e+3\", input: []byte(\"1e+3\"), _type: Number, value: []byte(\"1e+3\")},\n\t\t{name: \"1e-3\", input: []byte(\"1e-3\"), _type: Number, value: []byte(\"1e-3\")},\n\t\t{name: \"-1e3\", input: []byte(\"-1e3\"), _type: Number, value: []byte(\"-1e3\")},\n\t\t{name: \"-1e-3\", input: []byte(\"-1e-3\"), _type: Number, value: []byte(\"-1e-3\")},\n\n\t\t{name: \"1.123e3456\", input: []byte(\"1.123e3456\"), _type: Number, value: []byte(\"1.123e3456\")},\n\t\t{name: \"1.123e-3456\", input: []byte(\"1.123e-3456\"), _type: Number, value: []byte(\"1.123e-3456\")},\n\t\t{name: \"-1.123e3456\", input: []byte(\"-1.123e3456\"), _type: Number, value: []byte(\"-1.123e3456\")},\n\t\t{name: \"-1.123e-3456\", input: []byte(\"-1.123e-3456\"), _type: Number, value: []byte(\"-1.123e-3456\")},\n\n\t\t{name: \"1E3\", input: []byte(\"1E3\"), _type: Number, value: []byte(\"1E3\")},\n\t\t{name: \"1E-3\", input: []byte(\"1E-3\"), _type: Number, value: []byte(\"1E-3\")},\n\t\t{name: \"-1E3\", input: []byte(\"-1E3\"), _type: Number, value: []byte(\"-1E3\")},\n\t\t{name: \"-1E-3\", input: []byte(\"-1E-3\"), _type: Number, value: []byte(\"-1E-3\")},\n\n\t\t{name: \"1.123E3456\", input: []byte(\"1.123E3456\"), _type: Number, value: []byte(\"1.123E3456\")},\n\t\t{name: \"1.123E-3456\", input: []byte(\"1.123E-3456\"), _type: Number, value: []byte(\"1.123E-3456\")},\n\t\t{name: \"-1.123E3456\", input: []byte(\"-1.123E3456\"), _type: Number, value: []byte(\"-1.123E3456\")},\n\t\t{name: \"-1.123E-3456\", input: []byte(\"-1.123E-3456\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\n\t\t{name: \"-1.123E-3456 with spaces\", input: []byte(\" \\r -1.123E-3456 \\t\\n\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.name, err.Error())\n\t\t\t} else if root == nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t\t\t} else if root.nodeType != test._type {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t\t\t} else if !bytes.Equal(root.source(), test.value) {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_StringSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"white NL\", input: []byte(\"\\\"foo\\nbar\\\"\")},\n\t\t{name: \"white R\", input: []byte(\"\\\"foo\\rbar\\\"\")},\n\t\t{name: \"white Tab\", input: []byte(\"\\\"foo\\tbar\\\"\")},\n\t\t{name: \"wrong quotes\", input: []byte(\"'cat'\")},\n\t\t{name: \"double string\", input: []byte(\"\\\"Hello\\\" \\\"World\\\"\")},\n\t\t{name: \"quotes in quotes\", input: []byte(\"\\\"good \\\"cat\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"{}\", input: []byte(\"{}\"), _type: Object, value: []byte(\"{}\")},\n\t\t{name: `{ \\r\\n }`, input: []byte(\"{ \\r\\n }\"), _type: Object, value: []byte(\"{ \\r\\n }\")},\n\t\t{name: `{\"key\":1}`, input: []byte(`{\"key\":1}`), _type: Object, value: []byte(`{\"key\":1}`)},\n\t\t{name: `{\"key\":true}`, input: []byte(`{\"key\":true}`), _type: Object, value: []byte(`{\"key\":true}`)},\n\t\t{name: `{\"key\":\"value\"}`, input: []byte(`{\"key\":\"value\"}`), _type: Object, value: []byte(`{\"key\":\"value\"}`)},\n\t\t{name: `{\"foo\":\"bar\",\"baz\":\"foo\"}`, input: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`), _type: Object, value: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`)},\n\t\t{name: \"spaces\", input: []byte(` { \"foo\" : \"bar\" , \"baz\" : \"foo\" } `), _type: Object, value: []byte(`{ \"foo\" : \"bar\" , \"baz\" : \"foo\" }`)},\n\t\t{name: \"nested\", input: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`), _type: Object, value: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`)},\n\t\t{name: \"array\", input: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`), _type: Object, value: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`)},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"{{{\\\"key\\\": \\\"foo\\\"{{{{\"),\n\t\tsimpleCorrupted(\"}\"),\n\t\tsimpleCorrupted(\"{ }}}}}}}\"),\n\t\tsimpleCorrupted(\" }\"),\n\t\tsimpleCorrupted(\"{,}\"),\n\t\tsimpleCorrupted(\"{:}\"),\n\t\tsimpleCorrupted(\"{100000}\"),\n\t\tsimpleCorrupted(\"{1:1}\"),\n\t\tsimpleCorrupted(\"{'1:2,3:4'}\"),\n\t\tsimpleCorrupted(`{\"d\"}`),\n\t\tsimpleCorrupted(`{\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":}`),\n\t\tsimpleCorrupted(`{:\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":bar}`),\n\t\tsimpleCorrupted(`{\"foo\":\"bar\",}`),\n\t\tsimpleCorrupted(`{}{}`),\n\t\tsimpleCorrupted(`{},{}`),\n\t\tsimpleCorrupted(`{[},{]}`),\n\t\tsimpleCorrupted(`{[,]}`),\n\t\tsimpleCorrupted(`{[]}`),\n\t\tsimpleCorrupted(`{}1`),\n\t\tsimpleCorrupted(`1{}`),\n\t\tsimpleCorrupted(`{\"x\"::1}`),\n\t\tsimpleCorrupted(`{null:null}`),\n\t\tsimpleCorrupted(`{\"foo:\"bar\"}`),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NullSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"nul\", input: []byte(\"nul\")},\n\t\t{name: \"nil\", input: []byte(\"nil\")},\n\t\t{name: \"nill\", input: []byte(\"nill\")},\n\t\t{name: \"NILL\", input: []byte(\"NILL\")},\n\t\t{name: \"Null\", input: []byte(\"Null\")},\n\t\t{name: \"NULL\", input: []byte(\"NULL\")},\n\t\t{name: \"spaces\", input: []byte(\"Nu ll\")},\n\t\t{name: \"null1\", input: []byte(\"null1\")},\n\t\t{name: \"double\", input: []byte(\"null null\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"lower true\", input: []byte(\"true\"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"lower false\", input: []byte(\"false\"), _type: Boolean, value: []byte(\"false\")},\n\t\t{name: \"spaces true\", input: []byte(\" true\\r\\n \"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"spaces false\", input: []byte(\" false\\r\\n \"), _type: Boolean, value: []byte(\"false\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"tru\"),\n\t\tsimpleCorrupted(\"fals\"),\n\t\tsimpleCorrupted(\"tre\"),\n\t\tsimpleCorrupted(\"fal se\"),\n\t\tsimpleCorrupted(\"true false\"),\n\t\tsimpleCorrupted(\"True\"),\n\t\tsimpleCorrupted(\"TRUE\"),\n\t\tsimpleCorrupted(\"False\"),\n\t\tsimpleCorrupted(\"FALSE\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"[]\", input: []byte(\"[]\"), _type: Array, value: []byte(\"[]\")},\n\t\t{name: \"[1]\", input: []byte(\"[1]\"), _type: Array, value: []byte(\"[1]\")},\n\t\t{name: \"[1,2,3]\", input: []byte(\"[1,2,3]\"), _type: Array, value: []byte(\"[1,2,3]\")},\n\t\t{name: \"[1, 2, 3]\", input: []byte(\"[1, 2, 3]\"), _type: Array, value: []byte(\"[1, 2, 3]\")},\n\t\t{name: \"[1,[2],3]\", input: []byte(\"[1,[2],3]\"), _type: Array, value: []byte(\"[1,[2],3]\")},\n\t\t{name: \"[[],[],[]]\", input: []byte(\"[[],[],[]]\"), _type: Array, value: []byte(\"[[],[],[]]\")},\n\t\t{name: \"[[[[[]]]]]\", input: []byte(\"[[[[[]]]]]\"), _type: Array, value: []byte(\"[[[[[]]]]]\")},\n\t\t{name: \"[true,null,1,\\\"foo\\\",[]]\", input: []byte(\"[true,null,1,\\\"foo\\\",[]]\"), _type: Array, value: []byte(\"[true,null,1,\\\"foo\\\",[]]\")},\n\t\t{name: \"spaces\", input: []byte(\"\\n\\r [\\n1\\n ]\\r\\n\"), _type: Array, value: []byte(\"[\\n1\\n ]\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"[,]\"),\n\t\tsimpleCorrupted(\"[]\\\\\"),\n\t\tsimpleCorrupted(\"[1,]\"),\n\t\tsimpleCorrupted(\"[[]\"),\n\t\tsimpleCorrupted(\"[]]\"),\n\t\tsimpleCorrupted(\"1[]\"),\n\t\tsimpleCorrupted(\"[]1\"),\n\t\tsimpleCorrupted(\"[[]1]\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\n// Examples from https://json.org/example.html\nfunc TestUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tvalue string\n\t}{\n\t\t{\n\t\t\tname: \"glossary\",\n\t\t\tvalue: `{\n\t\t\t\t\"glossary\": {\n\t\t\t\t\t\"title\": \"example glossary\",\n\t\t\t\t\t\"GlossDiv\": {\n\t\t\t\t\t\t\"title\": \"S\",\n\t\t\t\t\t\t\"GlossList\": {\n\t\t\t\t\t\t\t\"GlossEntry\": {\n\t\t\t\t\t\t\t\t\"ID\": \"SGML\",\n\t\t\t\t\t\t\t\t\"SortAs\": \"SGML\",\n\t\t\t\t\t\t\t\t\"GlossTerm\": \"Standard Generalized Markup Language\",\n\t\t\t\t\t\t\t\t\"Acronym\": \"SGML\",\n\t\t\t\t\t\t\t\t\"Abbrev\": \"ISO 8879:1986\",\n\t\t\t\t\t\t\t\t\"GlossDef\": {\n\t\t\t\t\t\t\t\t\t\"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\n\t\t\t\t\t\t\t\t\t\"GlossSeeAlso\": [\"GML\", \"XML\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"GlossSee\": \"markup\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"menu\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"id\": \"file\",\n\t\t\t\t\"value\": \"File\",\n\t\t\t\t\"popup\": {\n\t\t\t\t \"menuitem\": [\n\t\t\t\t\t{\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n\t\t\t\t\t{\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n\t\t\t\t\t{\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n\t\t\t\t ]\n\t\t\t\t}\n\t\t\t}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"widget\",\n\t\t\tvalue: `{\"widget\": {\n\t\t\t\t\"debug\": \"on\",\n\t\t\t\t\"window\": {\n\t\t\t\t\t\"title\": \"Sample Konfabulator Widget\",\n\t\t\t\t\t\"name\": \"main_window\",\n\t\t\t\t\t\"width\": 500,\n\t\t\t\t\t\"height\": 500\n\t\t\t\t},\n\t\t\t\t\"image\": { \n\t\t\t\t\t\"src\": \"Images/Sun.png\",\n\t\t\t\t\t\"name\": \"sun1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 250,\n\t\t\t\t\t\"alignment\": \"center\"\n\t\t\t\t},\n\t\t\t\t\"text\": {\n\t\t\t\t\t\"data\": \"Click Here\",\n\t\t\t\t\t\"size\": 36,\n\t\t\t\t\t\"style\": \"bold\",\n\t\t\t\t\t\"name\": \"text1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 100,\n\t\t\t\t\t\"alignment\": \"center\",\n\t\t\t\t\t\"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n\t\t\t\t}\n\t\t\t}} `,\n\t\t},\n\t\t{\n\t\t\tname: \"web-app\",\n\t\t\tvalue: `{\"web-app\": {\n\t\t\t\t\"servlet\": [ \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxCDS\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.CDSServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"configGlossary:installationAt\": \"Philadelphia, PA\",\n\t\t\t\t\t \"configGlossary:adminEmail\": \"ksm@pobox.com\",\n\t\t\t\t\t \"configGlossary:poweredBy\": \"Cofax\",\n\t\t\t\t\t \"configGlossary:poweredByIcon\": \"/images/cofax.gif\",\n\t\t\t\t\t \"configGlossary:staticPath\": \"/content/static\",\n\t\t\t\t\t \"templateProcessorClass\": \"org.cofax.WysiwygTemplate\",\n\t\t\t\t\t \"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\",\n\t\t\t\t\t \"templatePath\": \"templates\",\n\t\t\t\t\t \"templateOverridePath\": \"\",\n\t\t\t\t\t \"defaultListTemplate\": \"listTemplate.htm\",\n\t\t\t\t\t \"defaultFileTemplate\": \"articleTemplate.htm\",\n\t\t\t\t\t \"useJSP\": false,\n\t\t\t\t\t \"jspListTemplate\": \"listTemplate.jsp\",\n\t\t\t\t\t \"jspFileTemplate\": \"articleTemplate.jsp\",\n\t\t\t\t\t \"cachePackageTagsTrack\": 200,\n\t\t\t\t\t \"cachePackageTagsStore\": 200,\n\t\t\t\t\t \"cachePackageTagsRefresh\": 60,\n\t\t\t\t\t \"cacheTemplatesTrack\": 100,\n\t\t\t\t\t \"cacheTemplatesStore\": 50,\n\t\t\t\t\t \"cacheTemplatesRefresh\": 15,\n\t\t\t\t\t \"cachePagesTrack\": 200,\n\t\t\t\t\t \"cachePagesStore\": 100,\n\t\t\t\t\t \"cachePagesRefresh\": 10,\n\t\t\t\t\t \"cachePagesDirtyRead\": 10,\n\t\t\t\t\t \"searchEngineListTemplate\": \"forSearchEnginesList.htm\",\n\t\t\t\t\t \"searchEngineFileTemplate\": \"forSearchEngines.htm\",\n\t\t\t\t\t \"searchEngineRobotsDb\": \"WEB-INF/robots.db\",\n\t\t\t\t\t \"useDataStore\": true,\n\t\t\t\t\t \"dataStoreClass\": \"org.cofax.SqlDataStore\",\n\t\t\t\t\t \"redirectionClass\": \"org.cofax.SqlRedirection\",\n\t\t\t\t\t \"dataStoreName\": \"cofax\",\n\t\t\t\t\t \"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\",\n\t\t\t\t\t \"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\",\n\t\t\t\t\t \"dataStoreUser\": \"sa\",\n\t\t\t\t\t \"dataStorePassword\": \"dataStoreTestQuery\",\n\t\t\t\t\t \"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\",\n\t\t\t\t\t \"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\",\n\t\t\t\t\t \"dataStoreInitConns\": 10,\n\t\t\t\t\t \"dataStoreMaxConns\": 100,\n\t\t\t\t\t \"dataStoreConnUsageLimit\": 100,\n\t\t\t\t\t \"dataStoreLogLevel\": \"debug\",\n\t\t\t\t\t \"maxUrlLength\": 500}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxEmail\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.EmailServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t\"mailHost\": \"mail1\",\n\t\t\t\t\t\"mailHostOverride\": \"mail2\"}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxAdmin\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.AdminServlet\"},\n\t\t\t \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"fileServlet\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.FileServlet\"},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxTools\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"templatePath\": \"toolstemplates/\",\n\t\t\t\t\t \"log\": 1,\n\t\t\t\t\t \"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\",\n\t\t\t\t\t \"logMaxSize\": \"\",\n\t\t\t\t\t \"dataLog\": 1,\n\t\t\t\t\t \"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\",\n\t\t\t\t\t \"dataLogMaxSize\": \"\",\n\t\t\t\t\t \"removePageCache\": \"/content/admin/remove?cache=pages\u0026id=\",\n\t\t\t\t\t \"removeTemplateCache\": \"/content/admin/remove?cache=templates\u0026id=\",\n\t\t\t\t\t \"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\",\n\t\t\t\t\t \"lookInContext\": 1,\n\t\t\t\t\t \"adminGroupID\": 4,\n\t\t\t\t\t \"betaServer\": true}}],\n\t\t\t\t\"servlet-mapping\": {\n\t\t\t\t \"cofaxCDS\": \"/\",\n\t\t\t\t \"cofaxEmail\": \"/cofaxutil/aemail/*\",\n\t\t\t\t \"cofaxAdmin\": \"/admin/*\",\n\t\t\t\t \"fileServlet\": \"/static/*\",\n\t\t\t\t \"cofaxTools\": \"/tools/*\"},\n\t\t\t \n\t\t\t\t\"taglib\": {\n\t\t\t\t \"taglib-uri\": \"cofax.tld\",\n\t\t\t\t \"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"SVG Viewer\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"header\": \"SVG Viewer\",\n\t\t\t\t\"items\": [\n\t\t\t\t\t{\"id\": \"Open\"},\n\t\t\t\t\t{\"id\": \"OpenNew\", \"label\": \"Open New\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n\t\t\t\t\t{\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n\t\t\t\t\t{\"id\": \"OriginalView\", \"label\": \"Original View\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Quality\"},\n\t\t\t\t\t{\"id\": \"Pause\"},\n\t\t\t\t\t{\"id\": \"Mute\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Find\", \"label\": \"Find...\"},\n\t\t\t\t\t{\"id\": \"FindAgain\", \"label\": \"Find Again\"},\n\t\t\t\t\t{\"id\": \"Copy\"},\n\t\t\t\t\t{\"id\": \"CopyAgain\", \"label\": \"Copy Again\"},\n\t\t\t\t\t{\"id\": \"CopySVG\", \"label\": \"Copy SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSVG\", \"label\": \"View SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSource\", \"label\": \"View Source\"},\n\t\t\t\t\t{\"id\": \"SaveAs\", \"label\": \"Save As\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Help\"},\n\t\t\t\t\t{\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n\t\t\t\t]\n\t\t\t}}`,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := Unmarshal([]byte(test.value))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshalSafe(t *testing.T) {\n\tjson := []byte(`{ \"store\": {\n\t\t\"book\": [ \n\t\t { \"category\": \"reference\",\n\t\t\t\"author\": \"Nigel Rees\",\n\t\t\t\"title\": \"Sayings of the Century\",\n\t\t\t\"price\": 8.95\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Evelyn Waugh\",\n\t\t\t\"title\": \"Sword of Honour\",\n\t\t\t\"price\": 12.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Herman Melville\",\n\t\t\t\"title\": \"Moby Dick\",\n\t\t\t\"isbn\": \"0-553-21311-3\",\n\t\t\t\"price\": 8.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"J. R. R. Tolkien\",\n\t\t\t\"title\": \"The Lord of the Rings\",\n\t\t\t\"isbn\": \"0-395-19395-8\",\n\t\t\t\"price\": 22.99\n\t\t }\n\t\t],\n\t\t\"bicycle\": {\n\t\t \"color\": \"red\",\n\t\t \"price\": 19.95\n\t\t}\n\t }\n\t}`)\n\tsafe, err := UnmarshalSafe(json)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t} else if safe == nil {\n\t\tt.Errorf(\"Error on Unmarshal: safe is nil\")\n\t} else {\n\t\troot, err := Unmarshal(json)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t} else if root == nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t\t} else if !bytes.Equal(root.source(), safe.source()) {\n\t\t\tt.Errorf(\"Error on UnmarshalSafe: values not same\")\n\t\t}\n\t}\n}\n\n// BenchmarkGoStdUnmarshal-8 \t 61698\t 19350 ns/op\t 288 B/op\t 6 allocs/op\n// BenchmarkUnmarshal-8 \t 45620\t 26165 ns/op\t 21889 B/op\t 367 allocs/op\n//\n// type bench struct {\n// \tName string `json:\"name\"`\n// \tValue int `json:\"value\"`\n// }\n\n// func BenchmarkGoStdUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\terr := json.Unmarshal(data, \u0026bench{})\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n\n// func BenchmarkUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\t_, err := Unmarshal(data)\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n" + }, + { + "name": "encode.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"math\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/json/ryu\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Marshal returns the JSON encoding of a Node.\nfunc Marshal(node *Node) ([]byte, error) {\n\tvar (\n\t\tbuf bytes.Buffer\n\t\tsVal string\n\t\tbVal bool\n\t\tnVal float64\n\t\toVal []byte\n\t\terr error\n\t)\n\n\tif node == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !node.modified \u0026\u0026 !node.ready() {\n\t\treturn nil, errors.New(\"node is not ready\")\n\t}\n\n\tif !node.modified \u0026\u0026 node.ready() {\n\t\tbuf.Write(node.source())\n\t}\n\n\tif node.modified {\n\t\tswitch node.nodeType {\n\t\tcase Null:\n\t\t\tbuf.Write(nullLiteral)\n\n\t\tcase Number:\n\t\t\tnVal, err = node.GetNumeric()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// ufmt does not support %g. by doing so, we need to check if the number is an integer\n\t\t\t// after then, apply the correct format for each float and integer numbers.\n\t\t\tif math.Mod(nVal, 1.0) == 0 {\n\t\t\t\t// must convert float to integer. otherwise it will be overflowed.\n\t\t\t\tnum := ufmt.Sprintf(\"%d\", int(nVal))\n\t\t\t\tbuf.WriteString(num)\n\t\t\t} else {\n\t\t\t\t// use ryu algorithm to convert float to string\n\t\t\t\tnum := ryu.FormatFloat64(nVal)\n\t\t\t\tbuf.WriteString(num)\n\t\t\t}\n\n\t\tcase String:\n\t\t\tsVal, err = node.GetString()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tquoted := ufmt.Sprintf(\"%s\", strconv.Quote(sVal))\n\t\t\tbuf.WriteString(quoted)\n\n\t\tcase Boolean:\n\t\t\tbVal, err = node.GetBool()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tbStr := ufmt.Sprintf(\"%t\", bVal)\n\t\t\tbuf.WriteString(bStr)\n\n\t\tcase Array:\n\t\t\tbuf.WriteByte(bracketOpen)\n\n\t\t\tfor i := 0; i \u003c len(node.next); i++ {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t}\n\n\t\t\t\telem, ok := node.next[strconv.Itoa(i)]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, ufmt.Errorf(\"array element %d is not found\", i)\n\t\t\t\t}\n\n\t\t\t\toVal, err = Marshal(elem)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(bracketClose)\n\n\t\tcase Object:\n\t\t\tbuf.WriteByte(curlyOpen)\n\n\t\t\tbVal = false\n\t\t\tfor k, v := range node.next {\n\t\t\t\tif bVal {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t} else {\n\t\t\t\t\tbVal = true\n\t\t\t\t}\n\n\t\t\t\tkey := ufmt.Sprintf(\"%s\", strconv.Quote(k))\n\t\t\t\tbuf.WriteString(key)\n\t\t\t\tbuf.WriteByte(colon)\n\n\t\t\t\toVal, err = Marshal(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(curlyClose)\n\t\t}\n\t}\n\n\treturn buf.Bytes(), nil\n}\n" + }, + { + "name": "encode_test.gno", + "body": "package json\n\nimport \"testing\"\n\nfunc TestMarshal_Primitive(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t}{\n\t\t{\n\t\t\tname: \"null\",\n\t\t\tnode: NullNode(\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"true\",\n\t\t\tnode: BoolNode(\"\", true),\n\t\t},\n\t\t{\n\t\t\tname: \"false\",\n\t\t\tnode: BoolNode(\"\", false),\n\t\t},\n\t\t{\n\t\t\tname: `\"string\"`,\n\t\t\tnode: StringNode(\"\", \"string\"),\n\t\t},\n\t\t{\n\t\t\tname: `\"one \\\"encoded\\\" string\"`,\n\t\t\tnode: StringNode(\"\", `one \"encoded\" string`),\n\t\t},\n\t\t{\n\t\t\tname: `{\"foo\":\"bar\"}`,\n\t\t\tnode: ObjectNode(\"\", map[string]*Node{\n\t\t\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tname: \"42\",\n\t\t\tnode: NumberNode(\"\", 42),\n\t\t},\n\t\t// TODO: fix output for not to use scientific notation\n\t\t{\n\t\t\tname: \"1.005e+02\",\n\t\t\tnode: NumberNode(\"\", 100.5),\n\t\t},\n\t\t{\n\t\t\tname: `[1,2,3]`,\n\t\t\tnode: ArrayNode(\"\", []*Node{\n\t\t\t\tNumberNode(\"0\", 1),\n\t\t\t\tNumberNode(\"2\", 2),\n\t\t\t\tNumberNode(\"3\", 3),\n\t\t\t}),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t} else if string(value) != test.name {\n\t\t\t\tt.Errorf(\"wrong result: '%s', expected '%s'\", value, test.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Object(t *testing.T) {\n\tnode := ObjectNode(\"\", map[string]*Node{\n\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\"baz\": NumberNode(\"baz\", 100500),\n\t\t\"qux\": NullNode(\"qux\"),\n\t})\n\n\tmustKey := []string{\"foo\", \"baz\", \"qux\"}\n\n\tvalue, err := Marshal(node)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\t// the order of keys in the map is not guaranteed\n\t// so we need to unmarshal the result and check the keys\n\tdecoded, err := Unmarshal(value)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\tfor _, key := range mustKey {\n\t\tif node, err := decoded.GetKey(key); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t} else {\n\t\t\tif node == nil {\n\t\t\t\tt.Errorf(\"node is nil\")\n\t\t\t} else if node.key == nil {\n\t\t\t\tt.Errorf(\"key is nil\")\n\t\t\t} else if *node.key != key {\n\t\t\t\tt.Errorf(\"wrong key: '%s', expected '%s'\", *node.key, key)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc valueNode(prev *Node, key string, typ ValueType, val interface{}) *Node {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: nil,\n\t\tkey: \u0026key,\n\t\tborders: [2]int{0, 0},\n\t\tvalue: val,\n\t\tmodified: true,\n\t}\n\n\tif val != nil {\n\t\tcurr.nodeType = typ\n\t}\n\n\treturn curr\n}\n\nfunc TestMarshal_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode func() (node *Node)\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"broken\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = Must(Unmarshal([]byte(`{}`)))\n\t\t\t\tnode.borders[1] = 0\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Numeric\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Number, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"String\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", String, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Bool\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Boolean, 1)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_1\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = ArrayNode(\"\", nil)\n\t\t\t\tnode.next[\"1\"] = NullNode(\"1\")\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_2\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ArrayNode(\"\", []*Node{valueNode(nil, \"\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Object\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ObjectNode(\"\", map[string]*Node{\"key\": valueNode(nil, \"key\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node())\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"expected error\")\n\t\t\t} else if len(value) != 0 {\n\t\t\t\tt.Errorf(\"wrong result\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Nil(t *testing.T) {\n\t_, err := Marshal(nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for nil node, but got nil\")\n\t}\n}\n\nfunc TestMarshal_NotModified(t *testing.T) {\n\tnode := \u0026Node{}\n\t_, err := Marshal(node)\n\tif err == nil {\n\t\tt.Error(\"Expected error for not modified node, but got nil\")\n\t}\n}\n\nfunc TestMarshalCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tnext: map[string]*Node{\n\t\t\t\"next\": nil,\n\t\t},\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tprev: node1,\n\t}\n\n\tnode1.next[\"next\"] = node2\n\n\t_, err := Marshal(node1)\n\tif err == nil {\n\t\tt.Error(\"Expected error for cycle reference, but got nil\")\n\t}\n}\n\nfunc TestMarshalNoCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tvalue: \"value1\",\n\t\tmodified: true,\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tvalue: \"value2\",\n\t\tmodified: true,\n\t}\n\n\t_, err := Marshal(node1)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\t_, err = Marshal(node2)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n}\n\nfunc stringPtr(s string) *string {\n\treturn \u0026s\n}\n" + }, + { + "name": "escape.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"unicode/utf8\"\n)\n\nconst (\n\tsupplementalPlanesOffset = 0x10000\n\thighSurrogateOffset = 0xD800\n\tlowSurrogateOffset = 0xDC00\n\tsurrogateEnd = 0xDFFF\n\tbasicMultilingualPlaneOffset = 0xFFFF\n\tbadHex = -1\n)\n\nvar hexLookupTable = [256]int{\n\t'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,\n\t'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,\n\t'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF,\n\t'a': 0xA, 'b': 0xB, 'c': 0xC, 'd': 0xD, 'e': 0xE, 'f': 0xF,\n\t// Fill unspecified index-value pairs with key and value of -1\n\t'G': -1, 'H': -1, 'I': -1, 'J': -1,\n\t'K': -1, 'L': -1, 'M': -1, 'N': -1,\n\t'O': -1, 'P': -1, 'Q': -1, 'R': -1,\n\t'S': -1, 'T': -1, 'U': -1, 'V': -1,\n\t'W': -1, 'X': -1, 'Y': -1, 'Z': -1,\n\t'g': -1, 'h': -1, 'i': -1, 'j': -1,\n\t'k': -1, 'l': -1, 'm': -1, 'n': -1,\n\t'o': -1, 'p': -1, 'q': -1, 'r': -1,\n\t's': -1, 't': -1, 'u': -1, 'v': -1,\n\t'w': -1, 'x': -1, 'y': -1, 'z': -1,\n}\n\nfunc h2i(c byte) int {\n\treturn hexLookupTable[c]\n}\n\n// Unescape takes an input byte slice, processes it to Unescape certain characters,\n// and writes the result into an output byte slice.\n//\n// it returns the processed slice and any error encountered during the Unescape operation.\nfunc Unescape(input, output []byte) ([]byte, error) {\n\t// find the index of the first backslash in the input slice.\n\tfirstBackslash := bytes.IndexByte(input, backSlash)\n\tif firstBackslash == -1 {\n\t\treturn input, nil\n\t}\n\n\t// ensure the output slice has enough capacity to hold the result.\n\tinputLen := len(input)\n\tif cap(output) \u003c inputLen {\n\t\toutput = make([]byte, inputLen)\n\t}\n\n\toutput = output[:inputLen]\n\tcopy(output, input[:firstBackslash])\n\n\tinput = input[firstBackslash:]\n\tbuf := output[firstBackslash:]\n\n\tfor len(input) \u003e 0 {\n\t\tinLen, bufLen, err := processEscapedUTF8(input, buf)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tinput = input[inLen:] // the number of bytes consumed in the input\n\t\tbuf = buf[bufLen:] // the number of bytes written to buf\n\n\t\t// find the next backslash in the remaining input\n\t\tnextBackslash := bytes.IndexByte(input, backSlash)\n\t\tif nextBackslash == -1 {\n\t\t\tcopy(buf, input)\n\t\t\tbuf = buf[len(input):]\n\t\t\tbreak\n\t\t}\n\n\t\tcopy(buf, input[:nextBackslash])\n\n\t\tinput = input[nextBackslash:]\n\t\tbuf = buf[nextBackslash:]\n\t}\n\n\treturn output[:len(output)-len(buf)], nil\n}\n\n// isSurrogatePair returns true if the rune is a surrogate pair.\n//\n// A surrogate pairs are used in UTF-16 encoding to encode characters\n// outside the Basic Multilingual Plane (BMP).\nfunc isSurrogatePair(r rune) bool {\n\treturn highSurrogateOffset \u003c= r \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// combineSurrogates reconstruct the original unicode code points in the\n// supplemental plane by combinin the high and low surrogate.\n//\n// The hight surrogate in the range from U+D800 to U+DBFF,\n// and the low surrogate in the range from U+DC00 to U+DFFF.\n//\n// The formula to combine the surrogates is:\n// (high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000\nfunc combineSurrogates(high, low rune) rune {\n\treturn ((high - highSurrogateOffset) \u003c\u003c 10) + (low - lowSurrogateOffset) + supplementalPlanesOffset\n}\n\n// deocdeSingleUnicodeEscape decodes a unicode escape sequence (e.g., \\uXXXX) into a rune.\nfunc decodeSingleUnicodeEscape(b []byte) (rune, bool) {\n\tif len(b) \u003c 6 {\n\t\treturn utf8.RuneError, false\n\t}\n\n\t// convert hex to decimal\n\th1, h2, h3, h4 := h2i(b[2]), h2i(b[3]), h2i(b[4]), h2i(b[5])\n\tif h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex {\n\t\treturn utf8.RuneError, false\n\t}\n\n\treturn rune(h1\u003c\u003c12 + h2\u003c\u003c8 + h3\u003c\u003c4 + h4), true\n}\n\n// decodeUnicodeEscape decodes a Unicode escape sequence from a byte slice.\nfunc decodeUnicodeEscape(b []byte) (rune, int) {\n\tr, ok := decodeSingleUnicodeEscape(b)\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// determine valid unicode escapes within the BMP\n\tif r \u003c= basicMultilingualPlaneOffset \u0026\u0026 !isSurrogatePair(r) {\n\t\treturn r, 6\n\t}\n\n\t// Decode the following escape sequence to verify a UTF-16 susergate pair.\n\tr2, ok := decodeSingleUnicodeEscape(b[6:])\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\n\tif r2 \u003c lowSurrogateOffset {\n\t\treturn utf8.RuneError, -1\n\t}\n\n\treturn combineSurrogates(r, r2), 12\n}\n\nvar escapeByteSet = [256]byte{\n\t'\"': doubleQuote,\n\t'\\\\': backSlash,\n\t'/': slash,\n\t'b': backSpace,\n\t'f': formFeed,\n\t'n': newLine,\n\t'r': carriageReturn,\n\t't': tab,\n}\n\n// Unquote takes a byte slice and unquotes it by removing\n// the surrounding quotes and unescaping the contents.\nfunc Unquote(s []byte, border byte) (string, bool) {\n\ts, ok := unquoteBytes(s, border)\n\treturn string(s), ok\n}\n\n// unquoteBytes takes a byte slice and unquotes it by removing\n// TODO: consider to move this function to the strconv package.\nfunc unquoteBytes(s []byte, border byte) ([]byte, bool) {\n\tif len(s) \u003c 2 || s[0] != border || s[len(s)-1] != border {\n\t\treturn nil, false\n\t}\n\n\ts = s[1 : len(s)-1]\n\n\tr := 0\n\tfor r \u003c len(s) {\n\t\tc := s[r]\n\n\t\tif c == backSlash || c == border || c \u003c 0x20 {\n\t\t\tbreak\n\t\t}\n\n\t\tif c \u003c utf8.RuneSelf {\n\t\t\tr++\n\t\t\tcontinue\n\t\t}\n\n\t\trr, size := utf8.DecodeRune(s[r:])\n\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tr += size\n\t}\n\n\tif r == len(s) {\n\t\treturn s, true\n\t}\n\n\tutfDoubleMax := utf8.UTFMax * 2\n\tb := make([]byte, len(s)+utfDoubleMax)\n\tw := copy(b, s[0:r])\n\n\tfor r \u003c len(s) {\n\t\tif w \u003e= len(b)-utf8.UTFMax {\n\t\t\tnb := make([]byte, utfDoubleMax+(2*len(b)))\n\t\t\tcopy(nb, b)\n\t\t\tb = nb\n\t\t}\n\n\t\tc := s[r]\n\t\tif c == backSlash {\n\t\t\tr++\n\t\t\tif r \u003e= len(s) {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tif s[r] == 'u' {\n\t\t\t\trr, res := decodeUnicodeEscape(s[r-1:])\n\t\t\t\tif res \u003c 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t\t\tr += 5\n\t\t\t} else {\n\t\t\t\tdecode := escapeByteSet[s[r]]\n\t\t\t\tif decode == 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tif decode == doubleQuote || decode == backSlash || decode == slash {\n\t\t\t\t\tdecode = s[r]\n\t\t\t\t}\n\n\t\t\t\tb[w] = decode\n\t\t\t\tr++\n\t\t\t\tw++\n\t\t\t}\n\t\t} else if c == border || c \u003c 0x20 {\n\t\t\treturn nil, false\n\t\t} else if c \u003c utf8.RuneSelf {\n\t\t\tb[w] = c\n\t\t\tr++\n\t\t\tw++\n\t\t} else {\n\t\t\trr, size := utf8.DecodeRune(s[r:])\n\n\t\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tr += size\n\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t}\n\t}\n\n\treturn b[:w], true\n}\n\n// processEscapedUTF8 processes the escape sequence in the given byte slice and\n// and converts them to UTF-8 characters. The function returns the length of the processed input and output.\n//\n// The input 'in' must contain the escape sequence to be processed,\n// and 'out' provides a space to store the converted characters.\n//\n// The function returns (input length, output length) if the escape sequence is correct.\n// Unicode escape sequences (e.g. \\uXXXX) are decoded to UTF-8, other default escape sequences are\n// converted to their corresponding special characters (e.g. \\n -\u003e newline).\n//\n// If the escape sequence is invalid, or if 'in' does not completely enclose the escape sequence,\n// function returns (-1, -1) to indicate an error.\nfunc processEscapedUTF8(in, out []byte) (int, int, error) {\n\tif len(in) \u003c 2 || in[0] != backSlash {\n\t\treturn -1, -1, errors.New(\"invalid escape sequence\")\n\t}\n\n\tescapeSeqLen := 2\n\tescapeChar := in[1]\n\n\tif escapeChar != 'u' {\n\t\tval := escapeByteSet[escapeChar]\n\t\tif val == 0 {\n\t\t\treturn -1, -1, errors.New(\"invalid escape sequence\")\n\t\t}\n\n\t\tout[0] = val\n\t\treturn escapeSeqLen, 1, nil\n\t}\n\n\tr, size := decodeUnicodeEscape(in)\n\tif size == -1 {\n\t\treturn -1, -1, errors.New(\"invalid escape sequence\")\n\t}\n\n\toutLen := utf8.EncodeRune(out, r)\n\n\treturn size, outLen, nil\n}\n" + }, + { + "name": "escape_test.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"unicode/utf8\"\n)\n\nfunc TestHexToInt(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tc byte\n\t\twant int\n\t}{\n\t\t{\"Digit 0\", '0', 0},\n\t\t{\"Digit 9\", '9', 9},\n\t\t{\"Uppercase A\", 'A', 10},\n\t\t{\"Uppercase F\", 'F', 15},\n\t\t{\"Lowercase a\", 'a', 10},\n\t\t{\"Lowercase f\", 'f', 15},\n\t\t{\"Invalid character1\", 'g', badHex},\n\t\t{\"Invalid character2\", 'G', badHex},\n\t\t{\"Invalid character3\", 'z', badHex},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := h2i(tt.c); got != tt.want {\n\t\t\t\tt.Errorf(\"h2i() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsSurrogatePair(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tr rune\n\t\texpected bool\n\t}{\n\t\t{\"high surrogate start\", 0xD800, true},\n\t\t{\"high surrogate end\", 0xDBFF, true},\n\t\t{\"low surrogate start\", 0xDC00, true},\n\t\t{\"low surrogate end\", 0xDFFF, true},\n\t\t{\"Non-surrogate\", 0x0000, false},\n\t\t{\"Non-surrogate 2\", 0xE000, false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := isSurrogatePair(tc.r); got != tc.expected {\n\t\t\t\tt.Errorf(\"isSurrogate() = %v, want %v\", got, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCombineSurrogates(t *testing.T) {\n\ttestCases := []struct {\n\t\thigh, low rune\n\t\texpected rune\n\t}{\n\t\t{0xD83D, 0xDC36, 0x1F436}, // 🐶 U+1F436 DOG FACE\n\t\t{0xD83D, 0xDE00, 0x1F600}, // 😀 U+1F600 GRINNING FACE\n\t\t{0xD83C, 0xDF03, 0x1F303}, // 🌃 U+1F303 NIGHT WITH STARS\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult := combineSurrogates(tc.high, tc.low)\n\t\tif result != tc.expected {\n\t\t\tt.Errorf(\"combineSurrogates(%U, %U) = %U; want %U\", tc.high, tc.low, result, tc.expected)\n\t\t}\n\t}\n}\n\nfunc TestDecodeSingleUnicodeEscape(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tisValid bool\n\t}{\n\t\t// valid unicode escape sequences\n\t\t{[]byte(`\\u0041`), 'A', true},\n\t\t{[]byte(`\\u03B1`), 'α', true},\n\t\t{[]byte(`\\u00E9`), 'é', true}, // valid non-English character\n\t\t{[]byte(`\\u0021`), '!', true}, // valid special character\n\t\t{[]byte(`\\uFF11`), '1', true},\n\t\t{[]byte(`\\uD83D`), 0xD83D, true},\n\t\t{[]byte(`\\uDE03`), 0xDE03, true},\n\n\t\t// invalid unicode escape sequences\n\t\t{[]byte(`\\u004`), utf8.RuneError, false}, // too short\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, false}, // invalid hex\n\t\t{[]byte(`\\u00G1`), utf8.RuneError, false}, // non-hex character\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult, isValid := decodeSingleUnicodeEscape(tc.input)\n\t\tif result != tc.expected || isValid != tc.isValid {\n\t\t\tt.Errorf(\"decodeSingleUnicodeEscape(%s) = (%U, %v); want (%U, %v)\", tc.input, result, isValid, tc.expected, tc.isValid)\n\t\t}\n\t}\n}\n\nfunc TestDecodeUnicodeEscape(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput string\n\t\texpected rune\n\t\tsize int\n\t}{\n\t\t{\"\\\\u0041\", 'A', 6},\n\t\t{\"\\\\u03B1\", 'α', 6},\n\t\t{\"\\\\u1F600\", 0x1F60, 6},\n\t\t{\"\\\\uD830\\\\uDE03\", 0x1C203, 12},\n\t\t{\"\\\\uD800\\\\uDC00\", 0x00010000, 12},\n\n\t\t{\"\\\\u004\", utf8.RuneError, -1},\n\t\t{\"\\\\uXYZW\", utf8.RuneError, -1},\n\t\t{\"\\\\uD83D\\\\u0041\", utf8.RuneError, -1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tr, size := decodeUnicodeEscape([]byte(tc.input))\n\t\tif r != tc.expected || size != tc.size {\n\t\t\tt.Errorf(\"decodeUnicodeEscape(%q) = (%U, %d); want (%U, %d)\", tc.input, r, size, tc.expected, tc.size)\n\t\t}\n\t}\n}\n\nfunc TestUnescapeToUTF8(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput []byte\n\t\texpectedIn int\n\t\texpectedOut int\n\t\tisError bool\n\t}{\n\t\t// valid escape sequences\n\t\t{[]byte(`\\n`), 2, 1, false},\n\t\t{[]byte(`\\t`), 2, 1, false},\n\t\t{[]byte(`\\u0041`), 6, 1, false},\n\t\t{[]byte(`\\u03B1`), 6, 2, false},\n\t\t{[]byte(`\\uD830\\uDE03`), 12, 4, false},\n\n\t\t// invalid escape sequences\n\t\t{[]byte(`\\`), -1, -1, true}, // incomplete escape sequence\n\t\t{[]byte(`\\x`), -1, -1, true}, // invalid escape character\n\t\t{[]byte(`\\u`), -1, -1, true}, // incomplete unicode escape sequence\n\t\t{[]byte(`\\u004`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uXYZW`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uD83D\\u0041`), -1, -1, true}, // invalid unicode escape sequence\n\t}\n\n\tfor _, tc := range testCases {\n\t\tinput := make([]byte, len(tc.input))\n\t\tcopy(input, tc.input)\n\t\toutput := make([]byte, utf8.UTFMax)\n\t\tinLen, outLen, err := processEscapedUTF8(input, output)\n\t\tif (err != nil) != tc.isError {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = %v; want %v\", tc.input, err, tc.isError)\n\t\t}\n\n\t\tif inLen != tc.expectedIn || outLen != tc.expectedOut {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = (%d, %d); want (%d, %d)\", tc.input, inLen, outLen, tc.expectedIn, tc.expectedOut)\n\t\t}\n\t}\n}\n\nfunc TestUnescape(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected []byte\n\t}{\n\t\t{\"NoEscape\", []byte(\"hello world\"), []byte(\"hello world\")},\n\t\t{\"SingleEscape\", []byte(\"hello\\\\nworld\"), []byte(\"hello\\nworld\")},\n\t\t{\"MultipleEscapes\", []byte(\"line1\\\\nline2\\\\r\\\\nline3\"), []byte(\"line1\\nline2\\r\\nline3\")},\n\t\t{\"UnicodeEscape\", []byte(\"snowman:\\\\u2603\"), []byte(\"snowman:\\u2603\")},\n\t\t{\"Complex\", []byte(\"tc\\\\n\\\\u2603\\\\r\\\\nend\"), []byte(\"tc\\n\\u2603\\r\\nend\")},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\toutput, _ := Unescape(tc.input, make([]byte, len(tc.input)+10))\n\t\t\tif !bytes.Equal(output, tc.expected) {\n\t\t\t\tt.Errorf(\"unescape(%q) = %q; want %q\", tc.input, output, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnquoteBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\tborder byte\n\t\texpected []byte\n\t\tok bool\n\t}{\n\t\t{[]byte(\"\\\"hello\\\"\"), '\"', []byte(\"hello\"), true},\n\t\t{[]byte(\"'hello'\"), '\\'', []byte(\"hello\"), true},\n\t\t{[]byte(\"\\\"hello\"), '\"', nil, false},\n\t\t{[]byte(\"hello\\\"\"), '\"', nil, false},\n\t\t{[]byte(\"\\\"he\\\\\\\"llo\\\"\"), '\"', []byte(\"he\\\"llo\"), true},\n\t\t{[]byte(\"\\\"he\\\\nllo\\\"\"), '\"', []byte(\"he\\nllo\"), true},\n\t\t{[]byte(\"\\\"\\\"\"), '\"', []byte(\"\"), true},\n\t\t{[]byte(\"''\"), '\\'', []byte(\"\"), true},\n\t\t{[]byte(\"\\\"\\\\u0041\\\"\"), '\"', []byte(\"A\"), true},\n\t\t{[]byte(`\"Hello, 世界\"`), '\"', []byte(\"Hello, 世界\"), true},\n\t\t{[]byte(`\"Hello, \\x80\"`), '\"', nil, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tresult, pass := unquoteBytes(tc.input, tc.border)\n\n\t\tif pass != tc.ok {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %v; want %v\", tc.input, pass, tc.ok)\n\t\t}\n\n\t\tif !bytes.Equal(result, tc.expected) {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t}\n\t}\n}\n" + }, + { + "name": "indent.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n)\n\n// indentGrowthFactor specifies the growth factor of indenting JSON input.\n// A factor no higher than 2 ensures that wasted space never exceeds 50%.\nconst indentGrowthFactor = 2\n\n// IndentJSON takes a JSON byte slice and a string for indentation,\n// then formats the JSON according to the specified indent string.\n// This function applies indentation rules as follows:\n//\n// 1. For top-level arrays and objects, no additional indentation is applied.\n//\n// 2. For nested structures like arrays within arrays or objects, indentation increases.\n//\n// 3. Indentation is applied after opening brackets ('[' or '{') and before closing brackets (']' or '}').\n//\n// 4. Commas and colons are handled appropriately to maintain valid JSON format.\n//\n// 5. Nested arrays within objects or arrays receive new lines and indentation based on their depth level.\n//\n// The function returns the formatted JSON as a byte slice and an error if any issues occurred during formatting.\nfunc Indent(data []byte, indent string) ([]byte, error) {\n\tvar (\n\t\tout bytes.Buffer\n\t\tlevel int\n\t\tinArray bool\n\t\tarrayDepth int\n\t)\n\n\tfor i := 0; i \u003c len(data); i++ {\n\t\tc := data[i] // current character\n\n\t\tswitch c {\n\t\tcase bracketOpen:\n\t\t\tarrayDepth++\n\t\t\tif arrayDepth \u003e 1 {\n\t\t\t\tlevel++ // increase the level if it's nested array\n\t\t\t\tinArray = true\n\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// case of the top-level array\n\t\t\t\tinArray = true\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase bracketClose:\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tlevel--\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tarrayDepth--\n\t\t\tif arrayDepth == 0 {\n\t\t\t\tinArray = false\n\t\t\t}\n\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase curlyOpen:\n\t\t\t// check if the empty object or array\n\t\t\t// we don't need to apply the indent when it's empty containers.\n\t\t\tif i+1 \u003c len(data) \u0026\u0026 data[i+1] == curlyClose {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\ti++ // skip next character\n\t\t\t\tif err := out.WriteByte(data[i]); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tlevel++\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase curlyClose:\n\t\t\tlevel--\n\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase comma, colon:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else if c == colon {\n\t\t\t\tif err := out.WriteByte(' '); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out.Bytes(), nil\n}\n\nfunc writeNewlineAndIndent(out *bytes.Buffer, level int, indent string) error {\n\tif err := out.WriteByte('\\n'); err != nil {\n\t\treturn err\n\t}\n\n\tidt := strings.Repeat(indent, level*indentGrowthFactor)\n\tif _, err := out.WriteString(idt); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n" + }, + { + "name": "indent_test.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestIndentJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\tindent string\n\t\texpected []byte\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tinput: []byte(`{}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`{}`),\n\t\t},\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tinput: []byte(`[]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`[]`),\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tinput: []byte(`{{}}`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"{\\n\\t\\t{}\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"nested array\",\n\t\t\tinput: []byte(`[[[]]]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"[[\\n\\t\\t[\\n\\t\\t\\t\\t\\n\\t\\t]\\n]]\"),\n\t\t},\n\t\t{\n\t\t\tname: \"top-level array\",\n\t\t\tinput: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t},\n\t\t{\n\t\t\tname: \"array of arrays\",\n\t\t\tinput: []byte(`[\"apple\",[\"banana\",\"cherry\"],\"date\"]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"[\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n],\\\"date\\\"]\"),\n\t\t},\n\n\t\t{\n\t\t\tname: \"nested array in object\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"fruits\\\": [\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n ],\\\"date\\\"]\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"complex nested structure\",\n\t\t\tinput: []byte(`{\"data\":{\"array\":[1,2,3],\"bool\":true,\"nestedArray\":[[\"a\",\"b\"],\"c\"]}}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"data\\\": {\\n \\\"array\\\": [1,2,3],\\\"bool\\\": true,\\\"nestedArray\\\": [[\\n \\\"a\\\",\\n \\\"b\\\"\\n ],\\\"c\\\"]\\n }\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"custom ident character\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \"*\",\n\t\t\texpected: []byte(\"{\\n**\\\"fruits\\\": [\\\"apple\\\",[\\n****\\\"banana\\\",\\n****\\\"cherry\\\"\\n**],\\\"date\\\"]\\n}\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual, err := Indent(tt.input, tt.indent)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"IndentJSON() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(actual, tt.expected) {\n\t\t\t\tt.Errorf(\"IndentJSON() = %q, want %q\", actual, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "internal.gno", + "body": "package json\n\n// Reference: https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c\n// Copyright (c) 2005 JSON.org\n\n// Go implementation is taken from: https://github.com/spyzhov/ajson/blob/master/internal/state.go\n\ntype (\n\tStates int8 // possible states of the parser\n\tClasses int8 // JSON string character types\n)\n\nconst __ = -1\n\n// enum classes\nconst (\n\tC_SPACE Classes = iota /* space */\n\tC_WHITE /* other whitespace */\n\tC_LCURB /* { */\n\tC_RCURB /* } */\n\tC_LSQRB /* [ */\n\tC_RSQRB /* ] */\n\tC_COLON /* : */\n\tC_COMMA /* , */\n\tC_QUOTE /* \" */\n\tC_BACKS /* \\ */\n\tC_SLASH /* / */\n\tC_PLUS /* + */\n\tC_MINUS /* - */\n\tC_POINT /* . */\n\tC_ZERO /* 0 */\n\tC_DIGIT /* 123456789 */\n\tC_LOW_A /* a */\n\tC_LOW_B /* b */\n\tC_LOW_C /* c */\n\tC_LOW_D /* d */\n\tC_LOW_E /* e */\n\tC_LOW_F /* f */\n\tC_LOW_L /* l */\n\tC_LOW_N /* n */\n\tC_LOW_R /* r */\n\tC_LOW_S /* s */\n\tC_LOW_T /* t */\n\tC_LOW_U /* u */\n\tC_ABCDF /* ABCDF */\n\tC_E /* E */\n\tC_ETC /* everything else */\n)\n\n// AsciiClasses array maps the 128 ASCII characters into character classes.\nvar AsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n// QuoteAsciiClasses is a HACK for single quote from AsciiClasses\nvar QuoteAsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_QUOTE,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n/*\nThe state codes.\n*/\nconst (\n\tGO States = iota /* start */\n\tOK /* ok */\n\tOB /* object */\n\tKE /* key */\n\tCO /* colon */\n\tVA /* value */\n\tAR /* array */\n\tST /* string */\n\tES /* escape */\n\tU1 /* u1 */\n\tU2 /* u2 */\n\tU3 /* u3 */\n\tU4 /* u4 */\n\tMI /* minus */\n\tZE /* zero */\n\tIN /* integer */\n\tDT /* dot */\n\tFR /* fraction */\n\tE1 /* e */\n\tE2 /* ex */\n\tE3 /* exp */\n\tT1 /* tr */\n\tT2 /* tru */\n\tT3 /* true */\n\tF1 /* fa */\n\tF2 /* fal */\n\tF3 /* fals */\n\tF4 /* false */\n\tN1 /* nu */\n\tN2 /* nul */\n\tN3 /* null */\n)\n\n// List of action codes.\n// these constants are defining an action that should be performed under certain conditions.\nconst (\n\tcl States = -2 /* colon */\n\tcm States = -3 /* comma */\n\tqt States = -4 /* quote */\n\tbo States = -5 /* bracket open */\n\tco States = -6 /* curly bracket open */\n\tbc States = -7 /* bracket close */\n\tcc States = -8 /* curly bracket close */\n\tec States = -9 /* curly bracket empty */\n)\n\n// StateTransitionTable is the state transition table takes the current state and the current symbol, and returns either\n// a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the\n// text the state is OK and if the mode is DONE.\nvar StateTransitionTable = [31][31]States{\n\t/*\n\t The state transition table takes the current state and the current symbol,\n\t and returns either a new state or an action. An action is represented as a\n\t negative number. A JSON text is accepted if at the end of the text the\n\t state is OK and if the mode is DONE.\n\t white 1-9 ABCDF etc\n\t space | { } [ ] : , \" \\ / + - . 0 | a b c d e f l n r s t u | E |*/\n\t/*start GO*/ {GO, GO, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*ok OK*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*object OB*/ {OB, OB, __, ec, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*key KE*/ {KE, KE, __, __, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*colon CO*/ {CO, CO, __, __, __, __, cl, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*value VA*/ {VA, VA, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*array AR*/ {AR, AR, co, __, bo, bc, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*string ST*/ {ST, __, ST, ST, ST, ST, ST, ST, qt, ES, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST},\n\t/*escape ES*/ {__, __, __, __, __, __, __, __, ST, ST, ST, __, __, __, __, __, __, ST, __, __, __, ST, __, ST, ST, __, ST, U1, __, __, __},\n\t/*u1 U1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U2, U2, U2, U2, U2, U2, U2, U2, __, __, __, __, __, __, U2, U2, __},\n\t/*u2 U2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U3, U3, U3, U3, U3, U3, U3, U3, __, __, __, __, __, __, U3, U3, __},\n\t/*u3 U3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U4, U4, U4, U4, U4, U4, U4, U4, __, __, __, __, __, __, U4, U4, __},\n\t/*u4 U4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ST, ST, ST, ST, ST, ST, ST, ST, __, __, __, __, __, __, ST, ST, __},\n\t/*minus MI*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ZE, IN, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*zero ZE*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, __, __, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*int IN*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, IN, IN, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*dot DT*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, FR, FR, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*frac FR*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, FR, FR, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*e E1*/ {__, __, __, __, __, __, __, __, __, __, __, E2, E2, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*ex E2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*exp E3*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*tr T1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T2, __, __, __, __, __, __},\n\t/*tru T2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T3, __, __, __},\n\t/*true T3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*fa F1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F2, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*fal F2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F3, __, __, __, __, __, __, __, __},\n\t/*fals F3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F4, __, __, __, __, __},\n\t/*false F4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*nu N1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N2, __, __, __},\n\t/*nul N2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N3, __, __, __, __, __, __, __, __},\n\t/*null N3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __},\n}\n" + }, + { + "name": "node.gno", + "body": "package json\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Node represents a JSON node.\ntype Node struct {\n\tprev *Node // prev is the parent node of the current node.\n\tnext map[string]*Node // next is the child nodes of the current node.\n\tkey *string // key holds the key of the current node in the parent node.\n\tdata []byte // byte slice of JSON data\n\tvalue interface{} // value holds the value of the current node.\n\tnodeType ValueType // NodeType holds the type of the current node. (Object, Array, String, Number, Boolean, Null)\n\tindex *int // index holds the index of the current node in the parent array node.\n\tborders [2]int // borders stores the start and end index of the current node in the data.\n\tmodified bool // modified indicates the current node is changed or not.\n}\n\n// NewNode creates a new node instance with the given parent node, buffer, type, and key.\nfunc NewNode(prev *Node, b *buffer, typ ValueType, key **string) (*Node, error) {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: b.data,\n\t\tborders: [2]int{b.index, 0},\n\t\tkey: *key,\n\t\tnodeType: typ,\n\t\tmodified: false,\n\t}\n\n\tif typ == Object || typ == Array {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\tif prev != nil {\n\t\tif prev.IsArray() {\n\t\t\tsize := len(prev.next)\n\t\t\tcurr.index = \u0026size\n\n\t\t\tprev.next[strconv.Itoa(size)] = curr\n\t\t} else if prev.IsObject() {\n\t\t\tif key == nil {\n\t\t\t\treturn nil, errors.New(\"key is required for object\")\n\t\t\t}\n\n\t\t\tprev.next[**key] = curr\n\t\t} else {\n\t\t\treturn nil, errors.New(\"invalid parent type\")\n\t\t}\n\t}\n\n\treturn curr, nil\n}\n\n// load retrieves the value of the current node.\nfunc (n *Node) load() interface{} {\n\treturn n.value\n}\n\n// Changed checks the current node is changed or not.\nfunc (n *Node) Changed() bool {\n\treturn n.modified\n}\n\n// Key returns the key of the current node.\nfunc (n *Node) Key() string {\n\tif n == nil || n.key == nil {\n\t\treturn \"\"\n\t}\n\n\treturn *n.key\n}\n\n// HasKey checks the current node has the given key or not.\nfunc (n *Node) HasKey(key string) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\t_, ok := n.next[key]\n\treturn ok\n}\n\n// GetKey returns the value of the given key from the current object node.\nfunc (n *Node) GetKey(key string) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif n.Type() != Object {\n\t\treturn nil, ufmt.Errorf(\"target node is not object type. got: %s\", n.Type().String())\n\t}\n\n\tvalue, ok := n.next[key]\n\tif !ok {\n\t\treturn nil, ufmt.Errorf(\"key not found: %s\", key)\n\t}\n\n\treturn value, nil\n}\n\n// MustKey returns the value of the given key from the current object node.\nfunc (n *Node) MustKey(key string) *Node {\n\tval, err := n.GetKey(key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// UniqueKeyLists traverses the current JSON nodes and collects all the unique keys.\nfunc (n *Node) UniqueKeyLists() []string {\n\tvar collectKeys func(*Node) []string\n\tcollectKeys = func(node *Node) []string {\n\t\tif node == nil || !node.IsObject() {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult := make(map[string]bool)\n\t\tfor key, childNode := range node.next {\n\t\t\tresult[key] = true\n\t\t\tchildKeys := collectKeys(childNode)\n\t\t\tfor _, childKey := range childKeys {\n\t\t\t\tresult[childKey] = true\n\t\t\t}\n\t\t}\n\n\t\tkeys := make([]string, 0, len(result))\n\t\tfor key := range result {\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t\treturn keys\n\t}\n\n\treturn collectKeys(n)\n}\n\n// Empty returns true if the current node is empty.\nfunc (n *Node) Empty() bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\treturn len(n.next) == 0\n}\n\n// Type returns the type (ValueType) of the current node.\nfunc (n *Node) Type() ValueType {\n\treturn n.nodeType\n}\n\n// Value returns the value of the current node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tval, err := root.MustKey(\"key\").Value()\n//\tif err != nil {\n//\t\tt.Errorf(\"Value returns error: %v\", err)\n//\t}\n//\n//\tresult: \"value\"\nfunc (n *Node) Value() (value interface{}, err error) {\n\tvalue = n.load()\n\n\tif value == nil {\n\t\tswitch n.nodeType {\n\t\tcase Null:\n\t\t\treturn nil, nil\n\n\t\tcase Number:\n\t\t\tvalue, err = ParseFloatLiteral(n.source())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase String:\n\t\t\tvar ok bool\n\t\t\tvalue, ok = Unquote(n.source(), doubleQuote)\n\t\t\tif !ok {\n\t\t\t\treturn \"\", errors.New(\"invalid string value\")\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase Boolean:\n\t\t\tif len(n.source()) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty boolean value\")\n\t\t\t}\n\n\t\t\tb := n.source()[0]\n\t\t\tvalue = b == 't' || b == 'T'\n\t\t\tn.value = value\n\n\t\tcase Array:\n\t\t\telems := make([]*Node, len(n.next))\n\n\t\t\tfor _, e := range n.next {\n\t\t\t\telems[*e.index] = e\n\t\t\t}\n\n\t\t\tvalue = elems\n\t\t\tn.value = value\n\n\t\tcase Object:\n\t\t\tobj := make(map[string]*Node, len(n.next))\n\n\t\t\tfor k, v := range n.next {\n\t\t\t\tobj[k] = v\n\t\t\t}\n\n\t\t\tvalue = obj\n\t\t\tn.value = value\n\t\t}\n\t}\n\n\treturn value, nil\n}\n\n// Delete removes the current node from the parent node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err := root.MustKey(\"key\").Delete(); err != nil {\n//\t\tt.Errorf(\"Delete returns error: %v\", err)\n//\t}\n//\n//\tresult: {} (empty object)\nfunc (n *Node) Delete() error {\n\tif n == nil {\n\t\treturn errors.New(\"can't delete nil node\")\n\t}\n\n\tif n.prev == nil {\n\t\treturn nil\n\t}\n\n\treturn n.prev.remove(n)\n}\n\n// Size returns the size (length) of the current array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.Size() != 2 {\n//\t\tt.Errorf(\"ArrayNode returns wrong size: %d\", root.Size())\n//\t}\nfunc (n *Node) Size() int {\n\tif n == nil {\n\t\treturn 0\n\t}\n\n\treturn len(n.next)\n}\n\n// Index returns the index of the current node in the parent array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.MustIndex(1).Index() != 1 {\n//\t\tt.Errorf(\"Index returns wrong index: %d\", root.MustIndex(1).Index())\n//\t}\n//\n// We can also use the index to the byte slice of the JSON data directly.\n//\n// Example:\n//\n//\troot := Unmarshal([]byte(`[\"foo\", 1]`))\n//\tif root == nil {\n//\t\tt.Errorf(\"Unmarshal returns nil\")\n//\t}\n//\n//\tif string(root.MustIndex(1).source()) != \"1\" {\n//\t\tt.Errorf(\"source returns wrong result: %s\", root.MustIndex(1).source())\n//\t}\nfunc (n *Node) Index() int {\n\tif n == nil || n.index == nil {\n\t\treturn -1\n\t}\n\n\treturn *n.index\n}\n\n// MustIndex returns the array element at the given index.\n//\n// If the index is negative, it returns the index is from the end of the array.\n// Also, it panics if the index is not found.\n//\n// check the Index method for detailed usage.\nfunc (n *Node) MustIndex(expectIdx int) *Node {\n\tval, err := n.GetIndex(expectIdx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// GetIndex returns the array element at the given index.\n//\n// if the index is negative, it returns the index is from the end of the array.\nfunc (n *Node) GetIndex(idx int) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !n.IsArray() {\n\t\treturn nil, errors.New(\"node is not array\")\n\t}\n\n\tif idx \u003e n.Size() {\n\t\treturn nil, errors.New(\"input index exceeds the array size\")\n\t}\n\n\tif idx \u003c 0 {\n\t\tidx += len(n.next)\n\t}\n\n\tchild, ok := n.next[strconv.Itoa(idx)]\n\tif !ok {\n\t\treturn nil, errors.New(\"index not found\")\n\t}\n\n\treturn child, nil\n}\n\n// DeleteIndex removes the array element at the given index.\nfunc (n *Node) DeleteIndex(idx int) error {\n\tnode, err := n.GetIndex(idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn n.remove(node)\n}\n\n// NullNode creates a new null type node.\n//\n// Usage:\n//\n//\t_ := NullNode(\"\")\nfunc NullNode(key string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: nil,\n\t\tnodeType: Null,\n\t\tmodified: true,\n\t}\n}\n\n// NumberNode creates a new number type node.\n//\n// Usage:\n//\n//\troot := NumberNode(\"\", 1)\n//\tif root == nil {\n//\t\tt.Errorf(\"NumberNode returns nil\")\n//\t}\nfunc NumberNode(key string, value float64) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Number,\n\t\tmodified: true,\n\t}\n}\n\n// StringNode creates a new string type node.\n//\n// Usage:\n//\n//\troot := StringNode(\"\", \"foo\")\n//\tif root == nil {\n//\t\tt.Errorf(\"StringNode returns nil\")\n//\t}\nfunc StringNode(key string, value string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: String,\n\t\tmodified: true,\n\t}\n}\n\n// BoolNode creates a new given boolean value node.\n//\n// Usage:\n//\n//\troot := BoolNode(\"\", true)\n//\tif root == nil {\n//\t\tt.Errorf(\"BoolNode returns nil\")\n//\t}\nfunc BoolNode(key string, value bool) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Boolean,\n\t\tmodified: true,\n\t}\n}\n\n// ArrayNode creates a new array type node.\n//\n// If the given value is nil, it creates an empty array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\nfunc ArrayNode(key string, value []*Node) *Node {\n\tcurr := \u0026Node{\n\t\tkey: \u0026key,\n\t\tnodeType: Array,\n\t\tmodified: true,\n\t}\n\n\tcurr.next = make(map[string]*Node, len(value))\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor i, v := range value {\n\t\t\tidx := i\n\t\t\tcurr.next[strconv.Itoa(i)] = v\n\n\t\t\tv.prev = curr\n\t\t\tv.index = \u0026idx\n\t\t}\n\t}\n\n\treturn curr\n}\n\n// ObjectNode creates a new object type node.\n//\n// If the given value is nil, it creates an empty object node.\n//\n// next is a map of key and value pairs of the object.\nfunc ObjectNode(key string, value map[string]*Node) *Node {\n\tcurr := \u0026Node{\n\t\tnodeType: Object,\n\t\tkey: \u0026key,\n\t\tnext: value,\n\t\tmodified: true,\n\t}\n\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor key, val := range value {\n\t\t\tvkey := key\n\t\t\tval.prev = curr\n\t\t\tval.key = \u0026vkey\n\t\t}\n\t} else {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\treturn curr\n}\n\n// IsArray returns true if the current node is array type.\nfunc (n *Node) IsArray() bool {\n\treturn n.nodeType == Array\n}\n\n// IsObject returns true if the current node is object type.\nfunc (n *Node) IsObject() bool {\n\treturn n.nodeType == Object\n}\n\n// IsNull returns true if the current node is null type.\nfunc (n *Node) IsNull() bool {\n\treturn n.nodeType == Null\n}\n\n// IsBool returns true if the current node is boolean type.\nfunc (n *Node) IsBool() bool {\n\treturn n.nodeType == Boolean\n}\n\n// IsString returns true if the current node is string type.\nfunc (n *Node) IsString() bool {\n\treturn n.nodeType == String\n}\n\n// IsNumber returns true if the current node is number type.\nfunc (n *Node) IsNumber() bool {\n\treturn n.nodeType == Number\n}\n\n// ready checks the current node is ready or not.\n//\n// the meaning of ready is the current node is parsed and has a valid value.\nfunc (n *Node) ready() bool {\n\treturn n.borders[1] != 0\n}\n\n// source returns the source of the current node.\nfunc (n *Node) source() []byte {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified \u0026\u0026 n.data != nil {\n\t\treturn (n.data)[n.borders[0]:n.borders[1]]\n\t}\n\n\treturn nil\n}\n\n// root returns the root node of the current node.\nfunc (n *Node) root() *Node {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tcurr := n\n\tfor curr.prev != nil {\n\t\tcurr = curr.prev\n\t}\n\n\treturn curr\n}\n\n// GetNull returns the null value if current node is null type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"null\"))\n//\tval, err := root.GetNull()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNull returns error: %v\", err)\n//\t}\n//\tif val != nil {\n//\t\tt.Errorf(\"GetNull returns wrong result: %v\", val)\n//\t}\nfunc (n *Node) GetNull() (interface{}, error) {\n\tif n == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !n.IsNull() {\n\t\treturn nil, errors.New(\"node is not null\")\n\t}\n\n\treturn nil, nil\n}\n\n// MustNull returns the null value if current node is null type.\n//\n// It panics if the current node is not null type.\nfunc (n *Node) MustNull() interface{} {\n\tv, err := n.GetNull()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetNumeric returns the numeric (int/float) value if current node is number type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"10.5\"))\n//\tval, err := root.GetNumeric()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNumeric returns error: %v\", err)\n//\t}\n//\tprintln(val) // 10.5\nfunc (n *Node) GetNumeric() (float64, error) {\n\tif n == nil {\n\t\treturn 0, errors.New(\"node is nil\")\n\t}\n\n\tif n.nodeType != Number {\n\t\treturn 0, errors.New(\"node is not number\")\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tv, ok := val.(float64)\n\tif !ok {\n\t\treturn 0, errors.New(\"node is not number\")\n\t}\n\n\treturn v, nil\n}\n\n// MustNumeric returns the numeric (int/float) value if current node is number type.\n//\n// It panics if the current node is not number type.\nfunc (n *Node) MustNumeric() float64 {\n\tv, err := n.GetNumeric()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetString returns the string value if current node is string type.\n//\n// Usage:\n//\n//\troot, err := Unmarshal([]byte(\"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n//\t}\n//\n//\tstr, err := root.GetString()\n//\tif err != nil {\n//\t\tt.Errorf(\"should retrieve string value: %s\", err)\n//\t}\n//\n//\tprintln(str) // \"foo\"\nfunc (n *Node) GetString() (string, error) {\n\tif n == nil {\n\t\treturn \"\", errors.New(\"string node is empty\")\n\t}\n\n\tif !n.IsString() {\n\t\treturn \"\", errors.New(\"node type is not string\")\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tv, ok := val.(string)\n\tif !ok {\n\t\treturn \"\", errors.New(\"node is not string\")\n\t}\n\n\treturn v, nil\n}\n\n// MustString returns the string value if current node is string type.\n//\n// It panics if the current node is not string type.\nfunc (n *Node) MustString() string {\n\tv, err := n.GetString()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetBool returns the boolean value if current node is boolean type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"true\"))\n//\tval, err := root.GetBool()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetBool returns error: %v\", err)\n//\t}\n//\tprintln(val) // true\nfunc (n *Node) GetBool() (bool, error) {\n\tif n == nil {\n\t\treturn false, errors.New(\"node is nil\")\n\t}\n\n\tif n.nodeType != Boolean {\n\t\treturn false, errors.New(\"node is not boolean\")\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tv, ok := val.(bool)\n\tif !ok {\n\t\treturn false, errors.New(\"node is not boolean\")\n\t}\n\n\treturn v, nil\n}\n\n// MustBool returns the boolean value if current node is boolean type.\n//\n// It panics if the current node is not boolean type.\nfunc (n *Node) MustBool() bool {\n\tv, err := n.GetBool()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetArray returns the array value if current node is array type.\n//\n// Usage:\n//\n//\t\troot := Must(Unmarshal([]byte(`[\"foo\", 1]`)))\n//\t\tarr, err := root.GetArray()\n//\t\tif err != nil {\n//\t\t\tt.Errorf(\"GetArray returns error: %v\", err)\n//\t\t}\n//\n//\t\tfor _, val := range arr {\n//\t\t\tprintln(val)\n//\t\t}\n//\n//\t result: \"foo\", 1\nfunc (n *Node) GetArray() ([]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif n.nodeType != Array {\n\t\treturn nil, errors.New(\"node is not array\")\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.([]*Node)\n\tif !ok {\n\t\treturn nil, errors.New(\"node is not array\")\n\t}\n\n\treturn v, nil\n}\n\n// MustArray returns the array value if current node is array type.\n//\n// It panics if the current node is not array type.\nfunc (n *Node) MustArray() []*Node {\n\tv, err := n.GetArray()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendArray appends the given values to the current array node.\n//\n// If the current node is not array type, it returns an error.\n//\n// Example 1:\n//\n//\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n//\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n//\t\tt.Errorf(\"should not return error: %s\", err)\n//\t}\n//\n//\tresult: [{\"foo\":\"bar\"}, null]\n//\n// Example 2:\n//\n//\troot := Must(Unmarshal([]byte(`[\"bar\", \"baz\"]`)))\n//\terr := root.AppendArray(NumberNode(\"\", 1), StringNode(\"\", \"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n//\t }\n//\n//\tresult: [\"bar\", \"baz\", 1, \"foo\"]\nfunc (n *Node) AppendArray(value ...*Node) error {\n\tif !n.IsArray() {\n\t\treturn errors.New(\"can't append value to non-array node\")\n\t}\n\n\tfor _, val := range value {\n\t\tif err := n.append(nil, val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ArrayEach executes the callback for each element in the JSON array.\n//\n// Usage:\n//\n//\tjsonArrayNode.ArrayEach(func(i int, valueNode *Node) {\n//\t ufmt.Println(i, valueNode)\n//\t})\nfunc (n *Node) ArrayEach(callback func(i int, target *Node)) {\n\tif n == nil || !n.IsArray() {\n\t\treturn\n\t}\n\n\tfor idx := 0; idx \u003c len(n.next); idx++ {\n\t\telement, err := n.GetIndex(idx)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcallback(idx, element)\n\t}\n}\n\n// GetObject returns the object value if current node is object type.\n//\n// Usage:\n//\n//\troot := Must(Unmarshal([]byte(`{\"key\": \"value\"}`)))\n//\tobj, err := root.GetObject()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetObject returns error: %v\", err)\n//\t}\n//\n//\tresult: map[string]*Node{\"key\": StringNode(\"key\", \"value\")}\nfunc (n *Node) GetObject() (map[string]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !n.IsObject() {\n\t\treturn nil, errors.New(\"node is not object\")\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.(map[string]*Node)\n\tif !ok {\n\t\treturn nil, errors.New(\"node is not object\")\n\t}\n\n\treturn v, nil\n}\n\n// MustObject returns the object value if current node is object type.\n//\n// It panics if the current node is not object type.\nfunc (n *Node) MustObject() map[string]*Node {\n\tv, err := n.GetObject()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendObject appends the given key and value to the current object node.\n//\n// If the current node is not object type, it returns an error.\nfunc (n *Node) AppendObject(key string, value *Node) error {\n\tif !n.IsObject() {\n\t\treturn errors.New(\"can't append value to non-object node\")\n\t}\n\n\tif err := n.append(\u0026key, value); err != nil {\n\t\treturn err\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ObjectEach executes the callback for each key-value pair in the JSON object.\n//\n// Usage:\n//\n//\tjsonObjectNode.ObjectEach(func(key string, valueNode *Node) {\n//\t ufmt.Println(key, valueNode)\n//\t})\nfunc (n *Node) ObjectEach(callback func(key string, value *Node)) {\n\tif n == nil || !n.IsObject() {\n\t\treturn\n\t}\n\n\tfor key, child := range n.next {\n\t\tcallback(key, child)\n\t}\n}\n\n// String converts the node to a string representation.\nfunc (n *Node) String() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified {\n\t\treturn string(n.source())\n\t}\n\n\tval, err := Marshal(n)\n\tif err != nil {\n\t\treturn \"error: \" + err.Error()\n\t}\n\n\treturn string(val)\n}\n\n// Path builds the path of the current node.\n//\n// For example:\n//\n//\t{ \"key\": { \"sub\": [ \"val1\", \"val2\" ] }}\n//\n// The path of \"val2\" is: $.key.sub[1]\nfunc (n *Node) Path() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tvar sb strings.Builder\n\n\tif n.prev == nil {\n\t\tsb.WriteString(\"$\")\n\t} else {\n\t\tsb.WriteString(n.prev.Path())\n\n\t\tif n.key != nil {\n\t\t\tsb.WriteString(\"['\" + n.Key() + \"']\")\n\t\t} else {\n\t\t\tsb.WriteString(\"[\" + strconv.Itoa(n.Index()) + \"]\")\n\t\t}\n\t}\n\n\treturn sb.String()\n}\n\n// mark marks the current node as modified.\nfunc (n *Node) mark() {\n\tnode := n\n\tfor node != nil \u0026\u0026 !node.modified {\n\t\tnode.modified = true\n\t\tnode = node.prev\n\t}\n}\n\n// isContainer checks the current node type is array or object.\nfunc (n *Node) isContainer() bool {\n\treturn n.IsArray() || n.IsObject()\n}\n\n// remove removes the value from the current container type node.\nfunc (n *Node) remove(v *Node) error {\n\tif !n.isContainer() {\n\t\treturn ufmt.Errorf(\n\t\t\t\"can't remove value from non-array or non-object node. got=%s\",\n\t\t\tn.Type().String(),\n\t\t)\n\t}\n\n\tif v.prev != n {\n\t\treturn errors.New(\"invalid parent node\")\n\t}\n\n\tn.mark()\n\tif n.IsArray() {\n\t\tdelete(n.next, strconv.Itoa(*v.index))\n\t\tn.dropIndex(*v.index)\n\t} else {\n\t\tdelete(n.next, *v.key)\n\t}\n\n\tv.prev = nil\n\treturn nil\n}\n\n// dropIndex rebase the index of current array node values.\nfunc (n *Node) dropIndex(idx int) {\n\tfor i := idx + 1; i \u003c= len(n.next); i++ {\n\t\tprv := i - 1\n\t\tif curr, ok := n.next[strconv.Itoa(i)]; ok {\n\t\t\tcurr.index = \u0026prv\n\t\t\tn.next[strconv.Itoa(prv)] = curr\n\t\t}\n\n\t\tdelete(n.next, strconv.Itoa(i))\n\t}\n}\n\n// append is a helper function to append the given value to the current container type node.\nfunc (n *Node) append(key *string, val *Node) error {\n\tif n.isSameOrParentNode(val) {\n\t\treturn errors.New(\"can't append same or parent node\")\n\t}\n\n\tif val.prev != nil {\n\t\tif err := val.prev.remove(val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tval.prev = n\n\tval.key = key\n\n\tif key == nil {\n\t\tsize := len(n.next)\n\t\tval.index = \u0026size\n\t\tn.next[strconv.Itoa(size)] = val\n\t} else {\n\t\tif old, ok := n.next[*key]; ok {\n\t\t\tif err := n.remove(old); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tn.next[*key] = val\n\t}\n\n\treturn nil\n}\n\nfunc (n *Node) isSameOrParentNode(nd *Node) bool {\n\treturn n == nd || n.isParentNode(nd)\n}\n\nfunc (n *Node) isParentNode(nd *Node) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\tfor curr := nd.prev; curr != nil; curr = curr.prev {\n\t\tif curr == n {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// cptrs returns the pointer of the given string value.\nfunc cptrs(cpy *string) *string {\n\tif cpy == nil {\n\t\treturn nil\n\t}\n\n\tval := *cpy\n\n\treturn \u0026val\n}\n\n// cptri returns the pointer of the given integer value.\nfunc cptri(i *int) *int {\n\tif i == nil {\n\t\treturn nil\n\t}\n\n\tval := *i\n\treturn \u0026val\n}\n\n// Must panics if the given node is not fulfilled the expectation.\n// Usage:\n//\n//\tnode := Must(Unmarshal([]byte(`{\"key\": \"value\"}`))\nfunc Must(root *Node, expect error) *Node {\n\tif expect != nil {\n\t\tpanic(expect)\n\t}\n\n\treturn root\n}\n" + }, + { + "name": "node_test.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tnilKey *string\n\tdummyKey = \"key\"\n)\n\ntype _args struct {\n\tprev *Node\n\tbuf *buffer\n\ttyp ValueType\n\tkey **string\n}\n\ntype simpleNode struct {\n\tname string\n\tnode *Node\n}\n\nfunc TestNode_CreateNewNode(t *testing.T) {\n\trel := \u0026dummyKey\n\n\ttests := []struct {\n\t\tname string\n\t\targs _args\n\t\texpectCurr *Node\n\t\texpectErr bool\n\t\texpectPanic bool\n\t}{\n\t\t{\n\t\t\tname: \"child for non container type\",\n\t\t\targs: _args{\n\t\t\t\tprev: BoolNode(\"\", true),\n\t\t\t\tbuf: newBuffer(make([]byte, 10)),\n\t\t\t\ttyp: Boolean,\n\t\t\t\tkey: \u0026rel,\n\t\t\t},\n\t\t\texpectCurr: nil,\n\t\t\texpectErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tif tt.expectPanic {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"%s panic occurred when not expected: %v\", tt.name, r)\n\t\t\t\t} else if tt.expectPanic {\n\t\t\t\t\tt.Errorf(\"%s expected panic but didn't occur\", tt.name)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tgot, err := NewNode(tt.args.prev, tt.args.buf, tt.args.typ, tt.args.key)\n\t\t\tif (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.expectErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.expectErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !compareNodes(got, tt.expectCurr) {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expectCurr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Value(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\t_type ValueType\n\t\texpected interface{}\n\t\terrExpected bool\n\t}{\n\t\t{name: \"null\", data: []byte(\"null\"), _type: Null, expected: nil},\n\t\t{name: \"1\", data: []byte(\"1\"), _type: Number, expected: float64(1)},\n\t\t{name: \".1\", data: []byte(\".1\"), _type: Number, expected: float64(.1)},\n\t\t{name: \"-.1e1\", data: []byte(\"-.1e1\"), _type: Number, expected: float64(-1)},\n\t\t{name: \"string\", data: []byte(\"\\\"foo\\\"\"), _type: String, expected: \"foo\"},\n\t\t{name: \"space\", data: []byte(\"\\\"foo bar\\\"\"), _type: String, expected: \"foo bar\"},\n\t\t{name: \"true\", data: []byte(\"true\"), _type: Boolean, expected: true},\n\t\t{name: \"invalid true\", data: []byte(\"tru\"), _type: Unknown, errExpected: true},\n\t\t{name: \"invalid false\", data: []byte(\"fals\"), _type: Unknown, errExpected: true},\n\t\t{name: \"false\", data: []byte(\"false\"), _type: Boolean, expected: false},\n\t\t{name: \"e1\", data: []byte(\"e1\"), _type: Unknown, errExpected: true},\n\t\t{name: \"1a\", data: []byte(\"1a\"), _type: Unknown, errExpected: true},\n\t\t{name: \"string error\", data: []byte(\"\\\"foo\\nbar\\\"\"), _type: String, errExpected: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcurr := \u0026Node{\n\t\t\t\tdata: tt.data,\n\t\t\t\tnodeType: tt._type,\n\t\t\t\tborders: [2]int{0, len(tt.data)},\n\t\t\t}\n\n\t\t\tgot, err := curr.Value()\n\t\t\tif err != nil {\n\t\t\t\tif !tt.errExpected {\n\t\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.errExpected)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Delete(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{\"foo\":\"bar\"}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tfoo := root.MustKey(\"foo\")\n\tif err := foo.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error while handling foo: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif value, err := Marshal(foo); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `\"bar\"` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif foo.prev != nil {\n\t\tt.Errorf(\"foo.prev should be nil\")\n\t}\n}\n\nfunc TestNode_ObjectNode(t *testing.T) {\n\tobjs := map[string]*Node{\n\t\t\"key1\": NullNode(\"null\"),\n\t\t\"key2\": NumberNode(\"answer\", 42),\n\t\t\"key3\": StringNode(\"string\", \"foobar\"),\n\t\t\"key4\": BoolNode(\"bool\", true),\n\t}\n\n\tnode := ObjectNode(\"test\", objs)\n\n\tif len(node.next) != len(objs) {\n\t\tt.Errorf(\"ObjectNode: want %v got %v\", len(objs), len(node.next))\n\t}\n\n\tfor k, v := range objs {\n\t\tif node.next[k] == nil {\n\t\t\tt.Errorf(\"ObjectNode: want %v got %v\", v, node.next[k])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendObject(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`{\"foo\":\"bar\",\"baz\":null}`))).AppendObject(\"biz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendArray should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.AppendObject(\"baz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendObject should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if isSameObject(string(value), `\"{\"foo\":\"bar\",\"baz\":null}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif err := root.AppendObject(\"biz\", NumberNode(\"\", 42)); err != nil {\n\t\tt.Errorf(\"AppendObject returns error: %v\", err)\n\t}\n\n\tval, err := Marshal(root)\n\tif err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif isSameObject(string(val), `\"{\"foo\":\"bar\",\"baz\":null,\"biz\":42}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(val))\n\t}\n}\n\nfunc TestNode_ArrayNode(t *testing.T) {\n\tarr := []*Node{\n\t\tNullNode(\"nil\"),\n\t\tNumberNode(\"num\", 42),\n\t\tStringNode(\"str\", \"foobar\"),\n\t\tBoolNode(\"bool\", true),\n\t}\n\n\tnode := ArrayNode(\"test\", arr)\n\n\tif len(node.next) != len(arr) {\n\t\tt.Errorf(\"ArrayNode: want %v got %v\", len(arr), len(node.next))\n\t}\n\n\tfor i, v := range arr {\n\t\tif node.next[strconv.Itoa(i)] == nil {\n\t\t\tt.Errorf(\"ArrayNode: want %v got %v\", v, node.next[strconv.Itoa(i)])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendArray(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`))).AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif err := root.AppendArray(\n\t\tNumberNode(\"\", 1),\n\t\tStringNode(\"\", \"foo\"),\n\t\tMust(Unmarshal([]byte(`[0,1,null,true,\"example\"]`))),\n\t\tMust(Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123}`))),\n\t); err != nil {\n\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null,1,\"foo\",[0,1,null,true,\"example\"],{\"foo\": true, \"bar\": null, \"baz\": 123}]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n}\n\n/******** value getter ********/\n\nfunc TestNode_GetBool(t *testing.T) {\n\troot, err := Unmarshal([]byte(`true`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetBool()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetBool(): %s\", err.Error())\n\t}\n\n\tif !value {\n\t\tt.Errorf(\"root.GetBool() is corrupted\")\n\t}\n}\n\nfunc TestNode_GetBool_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"literally null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetBool(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"true\", BoolNode(\"\", true)},\n\t\t{\"false\", BoolNode(\"\", false)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif !tt.node.IsBool() {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool_With_Unmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson []byte\n\t\twant bool\n\t}{\n\t\t{\"true\", []byte(\"true\"), true},\n\t\t{\"false\", []byte(\"false\"), true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.json)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\t\t}\n\n\t\t\tif root.IsBool() != tt.want {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar nullJson = []byte(`null`)\n\nfunc TestNode_GetNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue, err := root.GetNull()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting null, %s\", err)\n\t}\n\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNull_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"number node is null\", NumberNode(\"\", 42)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNull(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue := root.MustNull()\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNumeric_Float(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123.456`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123.456) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123.456, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Scientific_Notation(t *testing.T) {\n\troot, err := Unmarshal([]byte(`1e3`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(1000) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 1000, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_With_Unmarshal(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"string node\", StringNode(\"\", \"123\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNumeric(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetString(t *testing.T) {\n\troot, err := Unmarshal([]byte(`\"123foobar 3456\"`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t}\n\n\tvalue, err := root.GetString()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetString(): %s\", err)\n\t}\n\n\tif value != \"123foobar 3456\" {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %s\", value))\n\t}\n}\n\nfunc TestNode_GetString_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetString(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t}{\n\t\t{\"foo\", []byte(`\"foo\"`)},\n\t\t{\"foo bar\", []byte(`\"foo bar\"`)},\n\t\t{\"\", []byte(`\"\"`)},\n\t\t{\"안녕하세요\", []byte(`\"안녕하세요\"`)},\n\t\t{\"こんにちは\", []byte(`\"こんにちは\"`)},\n\t\t{\"你好\", []byte(`\"你好\"`)},\n\t\t{\"one \\\"encoded\\\" string\", []byte(`\"one \\\"encoded\\\" string\"`)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.data)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\t\t}\n\n\t\t\tvalue := root.MustString()\n\t\t\tif value != tt.name {\n\t\t\t\tt.Errorf(\"value is not matched. expected: %s, got: %s\", tt.name, value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_Array(t *testing.T) {\n\troot, err := Unmarshal([]byte(\" [1,[\\\"1\\\",[1,[1,2,3]]]]\\r\\n\"))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t}\n\n\tif root == nil {\n\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(\"Error on Unmarshal: wrong type\")\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err)\n\t} else if len(array) != 2 {\n\t\tt.Errorf(\"expected 2 elements, got %d\", len(array))\n\t} else if val, err := array[0].GetNumeric(); err != nil {\n\t\tt.Errorf(\"value of array[0] is not numeric. got: %v\", array[0].value)\n\t} else if val != 1 {\n\t\tt.Errorf(\"Error on array[0].GetNumeric(): expected to be '1', got: %v\", val)\n\t} else if val, err := array[1].GetArray(); err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err.Error())\n\t} else if len(val) != 2 {\n\t\tt.Errorf(\"Error on array[1].GetArray(): expected 2 elements, got %d\", len(val))\n\t} else if el, err := val[0].GetString(); err != nil {\n\t\tt.Errorf(\"error occurred while getting string, %s\", err.Error())\n\t} else if el != \"1\" {\n\t\tt.Errorf(\"Error on val[0].GetString(): expected to be '1', got: %s\", el)\n\t}\n}\n\nvar sampleArr = []byte(`[-1, 2, 3, 4, 5, 6]`)\n\nfunc TestNode_GetArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetArray(): %s\", err)\n\t}\n\n\tif len(array) != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"length is not matched. expected: 3, got: %d\", len(array)))\n\t}\n\n\tfor i, node := range array {\n\t\tfor j, val := range []int{-1, 2, 3, 4, 5, 6} {\n\t\t\tif i == j {\n\t\t\t\tif v, err := node.GetNumeric(); err != nil {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"Error on node.GetNumeric(): %s\", err))\n\t\t\t\t} else if v != float64(val) {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: %d, got: %v\", val, v))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNode_GetArray_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetArray(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(ufmt.Sprintf(\"Must be an array. got: %s\", root.Type().String()))\n\t}\n}\n\nfunc TestNode_ArrayEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []int\n\t}{\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tjson: `[]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"single element\",\n\t\t\tjson: `[42]`,\n\t\t\texpected: []int{42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements\",\n\t\t\tjson: `[1, 2, 3, 4, 5]`,\n\t\t\texpected: []int{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements but all values are same\",\n\t\t\tjson: `[1, 1, 1, 1, 1]`,\n\t\t\texpected: []int{1, 1, 1, 1, 1},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements with non-numeric values\",\n\t\t\tjson: `[\"a\", \"b\", \"c\", \"d\", \"e\"]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"non-array node\",\n\t\t\tjson: `{\"not\": \"an array\"}`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"array containing numeric and non-numeric elements\",\n\t\t\tjson: `[\"1\", 2, 3, \"4\", 5, \"6\"]`,\n\t\t\texpected: []int{2, 3, 5},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tvar result []int // callback result\n\t\t\troot.ArrayEach(func(index int, element *Node) {\n\t\t\t\tif val, err := strconv.Atoi(element.String()); err == nil {\n\t\t\t\t\tresult = append(result, val)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d elements, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i, val := range result {\n\t\t\t\tif val != tc.expected[i] {\n\t\t\t\t\tt.Errorf(\"%s: expected value at index %d to be %d, got %d\", tc.name, i, tc.expected[i], val)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Key(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123, \"biz\": [1,2,3]}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tobj := root.MustObject()\n\tfor key, node := range obj {\n\t\tif key != node.Key() {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", node.Key(), key)\n\t\t}\n\t}\n\n\tkeys := []string{\"foo\", \"bar\", \"baz\", \"biz\"}\n\tfor _, key := range keys {\n\t\tif obj[key].Key() != key {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", obj[key].Key(), key)\n\t\t}\n\t}\n\n\t// TODO: resolve stack overflow\n\t// if root.MustKey(\"foo\").Clone().Key() != \"\" {\n\t// \tt.Errorf(\"wrong key found for cloned key\")\n\t// }\n\n\tif (*Node)(nil).Key() != \"\" {\n\t\tt.Errorf(\"wrong key found for nil node\")\n\t}\n}\n\nfunc TestNode_Size(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tsize := root.Size()\n\tif size != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 6. got: %v\", size))\n\t}\n\n\tif (*Node)(nil).Size() != 0 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 0. got: %v\", (*Node)(nil).Size()))\n\t}\n}\n\nfunc TestNode_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tarr := root.MustArray()\n\tfor i, node := range arr {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_Index_Fail(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant int\n\t}{\n\t\t{\"nil node\", (*Node)(nil), -1},\n\t\t{\"null node\", NullNode(\"\"), -1},\n\t\t{\"object node\", ObjectNode(\"\", nil), -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Index(); got != tt.want {\n\t\t\t\tt.Errorf(\"Index() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetIndex(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`)))\n\texpected := []int{1, 2, 3, 4, 5, 6}\n\n\tif len(expected) != root.Size() {\n\t\tt.Errorf(\"length is not matched. expected: %d, got: %d\", len(expected), root.Size())\n\t}\n\n\t// TODO: if length exceeds, stack overflow occurs. need to fix\n\tfor i, v := range expected {\n\t\tval, err := root.GetIndex(i)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error occurred while getting index %d, %s\", i, err)\n\t\t}\n\n\t\tif val.MustNumeric() != float64(v) {\n\t\t\tt.Errorf(\"value is not matched. expected: %d, got: %v\", v, val.MustNumeric())\n\t\t}\n\t}\n}\n\nfunc TestNode_GetIndex_InputIndex_Exceed_Original_Node_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\t_, err = root.GetIndex(10)\n\tif err == nil {\n\t\tt.Errorf(\"GetIndex should return error\")\n\t}\n}\n\nfunc TestNode_DeleteIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\texpected string\n\t\tindex int\n\t\tok bool\n\t}{\n\t\t{`null`, ``, 0, false},\n\t\t{`1`, ``, 0, false},\n\t\t{`{}`, ``, 0, false},\n\t\t{`{\"foo\":\"bar\"}`, ``, 0, false},\n\t\t{`true`, ``, 0, false},\n\t\t{`[]`, ``, 0, false},\n\t\t{`[]`, ``, -1, false},\n\t\t{`[1]`, `[]`, 0, true},\n\t\t{`[{}]`, `[]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, -1, true},\n\t\t{`[{}, [], 42]`, `[[], 42]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, 42]`, 1, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, 2, true},\n\t\t{`[{}, [], 42]`, ``, 10, false},\n\t\t{`[{}, [], 42]`, ``, -10, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot := Must(Unmarshal([]byte(tt.name)))\n\t\t\terr := root.DeleteIndex(tt.index)\n\t\t\tif err != nil \u0026\u0026 tt.ok {\n\t\t\t\tt.Errorf(\"DeleteIndex returns error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetKey(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tvalue, err := root.GetKey(\"foo\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\tif value.MustBool() != true {\n\t\tt.Errorf(\"value is not matched. expected: true, got: %v\", value.MustBool())\n\t}\n\n\tvalue, err = root.GetKey(\"bar\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\t_, err = root.GetKey(\"baz\")\n\tif err == nil {\n\t\tt.Errorf(\"key baz is not exist. must be failed\")\n\t}\n\n\tif value.MustNull() != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value.MustNull())\n\t}\n}\n\nfunc TestNode_GetKey_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetKey(\"\"); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetUniqueKeyList(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname: \"simple foo/bar\",\n\t\t\tjson: `{\"foo\": true, \"bar\": null}`,\n\t\t\texpected: []string{\"foo\", \"bar\"},\n\t\t},\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tjson: `{\n\t\t\t\t\"outer\": {\n\t\t\t\t\t\"inner\": {\n\t\t\t\t\t\t\"key\": \"value\"\n\t\t\t\t\t},\n\t\t\t\t\t\"array\": [1, 2, 3]\n\t\t\t\t},\n\t\t\t\t\"another\": \"item\"\n\t\t\t}`,\n\t\t\texpected: []string{\"outer\", \"inner\", \"key\", \"array\", \"another\"},\n\t\t},\n\t\t{\n\t\t\tname: \"complex object\",\n\t\t\tjson: `{\n\t\t\t\t\"Image\": {\n\t\t\t\t\t\"Width\": 800,\n\t\t\t\t\t\"Height\": 600,\n\t\t\t\t\t\"Title\": \"View from 15th Floor\",\n\t\t\t\t\t\"Thumbnail\": {\n\t\t\t\t\t\t\"Url\": \"http://www.example.com/image/481989943\",\n\t\t\t\t\t\t\"Height\": 125,\n\t\t\t\t\t\t\"Width\": 100\n\t\t\t\t\t},\n\t\t\t\t\t\"Animated\": false,\n\t\t\t\t\t\"IDs\": [116, 943, 234, 38793]\n\t\t\t\t}\n\t\t\t}`,\n\t\t\texpected: []string{\"Image\", \"Width\", \"Height\", \"Title\", \"Thumbnail\", \"Url\", \"Animated\", \"IDs\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tt.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error occurred while unmarshal\")\n\t\t\t}\n\n\t\t\tvalue := root.UniqueKeyLists()\n\t\t\tif len(value) != len(tt.expected) {\n\t\t\t\tt.Errorf(\"%s length must be %v. got: %v. retrieved keys: %s\", tt.name, len(tt.expected), len(value), value)\n\t\t\t}\n\n\t\t\tfor _, key := range value {\n\t\t\t\tif !contains(tt.expected, key) {\n\t\t\t\t\tt.Errorf(\"EachKey() must be in %v. got: %v\", tt.expected, key)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TODO: resolve stack overflow\nfunc TestNode_IsEmpty(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\texpected bool\n\t}{\n\t\t{\"nil node\", (*Node)(nil), false}, // nil node is not empty.\n\t\t// {\"null node\", NullNode(\"\"), true},\n\t\t{\"empty object\", ObjectNode(\"\", nil), true},\n\t\t{\"empty array\", ArrayNode(\"\", nil), true},\n\t\t{\"non-empty object\", ObjectNode(\"\", map[string]*Node{\"foo\": BoolNode(\"foo\", true)}), false},\n\t\t{\"non-empty array\", ArrayNode(\"\", []*Node{BoolNode(\"0\", true)}), false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Empty(); got != tt.expected {\n\t\t\t\tt.Errorf(\"%s = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Index_EmptyList(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tarray := root.MustArray()\n\tfor i, node := range array {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_GetObject(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true,\"bar\": null}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetObject()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetObject(): %s\", err.Error())\n\t}\n\n\tif _, ok := value[\"foo\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: foo\")\n\t}\n\n\tif _, ok := value[\"bar\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: bar\")\n\t}\n}\n\nfunc TestNode_GetObject_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"get object from null node\", NullNode(\"\")},\n\t\t{\"not object node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetObject(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ObjectEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected map[string]int\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t\t{\n\t\t\tname: \"single key-value pair\",\n\t\t\tjson: `{\"key\": 42}`,\n\t\t\texpected: map[string]int{\"key\": 42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs\",\n\t\t\tjson: `{\"one\": 1, \"two\": 2, \"three\": 3}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"two\": 2, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs with some non-numeric values\",\n\t\t\tjson: `{\"one\": 1, \"two\": \"2\", \"three\": 3, \"four\": \"4\"}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"non-object node\",\n\t\t\tjson: `[\"not\", \"an\", \"object\"]`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tresult := make(map[string]int)\n\t\t\troot.ObjectEach(func(key string, value *Node) {\n\t\t\t\t// extract integer values from the object\n\t\t\t\tif val, err := strconv.Atoi(value.String()); err == nil {\n\t\t\t\t\tresult[key] = val\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d key-value pairs, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor key, val := range tc.expected {\n\t\t\t\tif result[key] != val {\n\t\t\t\t\tt.Errorf(\"%s: expected value for key %s to be %d, got %d\", tc.name, key, val, result[key])\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ExampleMust(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot := Must(Unmarshal(data))\n\tif root.Size() != 1 {\n\t\tt.Errorf(\"root.Size() must be 1. got: %v\", root.Size())\n\t}\n\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n\t// Output:\n\t// Object has 1 inheritors inside\n}\n\n// Calculate AVG price from different types of objects, JSON from: https://goessner.net/articles/JsonPath/index.html#e3\nfunc TestExampleUnmarshal(t *testing.T) {\n\tdata := []byte(`{ \"store\": {\n \"book\": [ \n { \"category\": \"reference\",\n \"author\": \"Nigel Rees\",\n \"title\": \"Sayings of the Century\",\n \"price\": 8.95\n },\n { \"category\": \"fiction\",\n \"author\": \"Evelyn Waugh\",\n \"title\": \"Sword of Honour\",\n \"price\": 12.99\n },\n { \"category\": \"fiction\",\n \"author\": \"Herman Melville\",\n \"title\": \"Moby Dick\",\n \"isbn\": \"0-553-21311-3\",\n \"price\": 8.99\n },\n { \"category\": \"fiction\",\n \"author\": \"J. R. R. Tolkien\",\n \"title\": \"The Lord of the Rings\",\n \"isbn\": \"0-395-19395-8\",\n \"price\": 22.99\n }\n ],\n \"bicycle\": { \"color\": \"red\",\n \"price\": 19.95\n },\n \"tools\": null\n }\n}`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred when unmarshal\")\n\t}\n\n\tstore := root.MustKey(\"store\").MustObject()\n\n\tvar prices float64\n\tsize := 0\n\tfor _, objects := range store {\n\t\tif objects.IsArray() \u0026\u0026 objects.Size() \u003e 0 {\n\t\t\tsize += objects.Size()\n\t\t\tfor _, object := range objects.MustArray() {\n\t\t\t\tprices += object.MustKey(\"price\").MustNumeric()\n\t\t\t}\n\t\t} else if objects.IsObject() \u0026\u0026 objects.HasKey(\"price\") {\n\t\t\tsize++\n\t\t\tprices += objects.MustKey(\"price\").MustNumeric()\n\t\t}\n\t}\n\n\tresult := int(prices / float64(size))\n\tufmt.Sprintf(\"AVG price: %d\", result)\n}\n\nfunc TestNode_ExampleMust_panic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"The code did not panic\")\n\t\t}\n\t}()\n\tdata := []byte(`{]`)\n\troot := Must(Unmarshal(data))\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n}\n\nfunc TestNode_Path(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tif root.Path() != \"$\" {\n\t\tt.Errorf(\"Wrong root.Path()\")\n\t}\n\n\telement := root.MustKey(\"Image\").MustKey(\"Thumbnail\").MustKey(\"Url\")\n\tif element.Path() != \"$['Image']['Thumbnail']['Url']\" {\n\t\tt.Errorf(\"Wrong path found: %s\", element.Path())\n\t}\n\n\tif (*Node)(nil).Path() != \"\" {\n\t\tt.Errorf(\"Wrong (nil).Path()\")\n\t}\n}\n\nfunc TestNode_Path2(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"Node with key\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tkey: func() *string { s := \"key\"; return \u0026s }(),\n\t\t\t},\n\t\t\twant: \"$['key']\",\n\t\t},\n\t\t{\n\t\t\tname: \"Node with index\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tindex: func() *int { i := 1; return \u0026i }(),\n\t\t\t},\n\t\t\twant: \"$[1]\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Path(); got != tt.want {\n\t\t\t\tt.Errorf(\"Path() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Root(t *testing.T) {\n\troot := \u0026Node{}\n\tchild := \u0026Node{prev: root}\n\tgrandChild := \u0026Node{prev: child}\n\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant *Node\n\t}{\n\t\t{\n\t\t\tname: \"Root node\",\n\t\t\tnode: root,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Child node\",\n\t\t\tnode: child,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Grandchild node\",\n\t\t\tnode: grandChild,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Node is nil\",\n\t\t\tnode: nil,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.root(); got != tt.want {\n\t\t\t\tt.Errorf(\"root() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc contains(slice []string, item string) bool {\n\tfor _, a := range slice {\n\t\tif a == item {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ignore the sequence of keys by ordering them.\n// need to avoid import encoding/json and reflect package.\n// because gno does not support them for now.\n// TODO: use encoding/json to compare the result after if possible in gno.\nfunc isSameObject(a, b string) bool {\n\taPairs := strings.Split(strings.Trim(a, \"{}\"), \",\")\n\tbPairs := strings.Split(strings.Trim(b, \"{}\"), \",\")\n\n\taMap := make(map[string]string)\n\tbMap := make(map[string]string)\n\tfor _, pair := range aPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\taMap[key] = value\n\t}\n\tfor _, pair := range bPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\tbMap[key] = value\n\t}\n\n\taKeys := make([]string, 0, len(aMap))\n\tbKeys := make([]string, 0, len(bMap))\n\tfor k := range aMap {\n\t\taKeys = append(aKeys, k)\n\t}\n\n\tfor k := range bMap {\n\t\tbKeys = append(bKeys, k)\n\t}\n\n\tsort.Strings(aKeys)\n\tsort.Strings(bKeys)\n\n\tif len(aKeys) != len(bKeys) {\n\t\treturn false\n\t}\n\n\tfor i := range aKeys {\n\t\tif aKeys[i] != bKeys[i] {\n\t\t\treturn false\n\t\t}\n\n\t\tif aMap[aKeys[i]] != bMap[bKeys[i]] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc compareNodes(n1, n2 *Node) bool {\n\tif n1 == nil || n2 == nil {\n\t\treturn n1 == n2\n\t}\n\n\tif n1.key != n2.key {\n\t\treturn false\n\t}\n\n\tif !bytes.Equal(n1.data, n2.data) {\n\t\treturn false\n\t}\n\n\tif n1.index != n2.index {\n\t\treturn false\n\t}\n\n\tif n1.borders != n2.borders {\n\t\treturn false\n\t}\n\n\tif n1.modified != n2.modified {\n\t\treturn false\n\t}\n\n\tif n1.nodeType != n2.nodeType {\n\t\treturn false\n\t}\n\n\tif !compareNodes(n1.prev, n2.prev) {\n\t\treturn false\n\t}\n\n\tif len(n1.next) != len(n2.next) {\n\t\treturn false\n\t}\n\n\tfor k, v := range n1.next {\n\t\tif !compareNodes(v, n2.next[k]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n" + }, + { + "name": "parser.gno", + "body": "package json\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strconv\"\n\n\tel \"gno.land/p/demo/json/eisel_lemire\"\n)\n\nconst (\n\tabsMinInt64 = 1 \u003c\u003c 63\n\tmaxInt64 = absMinInt64 - 1\n\tmaxUint64 = 1\u003c\u003c64 - 1\n)\n\nconst unescapeStackBufSize = 64\n\n// PaseStringLiteral parses a string from the given byte slice.\nfunc ParseStringLiteral(data []byte) (string, error) {\n\tvar buf [unescapeStackBufSize]byte\n\n\tbf, err := Unescape(data, buf[:])\n\tif err != nil {\n\t\treturn \"\", errors.New(\"invalid string input found while parsing string value\")\n\t}\n\n\treturn string(bf), nil\n}\n\n// ParseBoolLiteral parses a boolean value from the given byte slice.\nfunc ParseBoolLiteral(data []byte) (bool, error) {\n\tswitch {\n\tcase bytes.Equal(data, trueLiteral):\n\t\treturn true, nil\n\tcase bytes.Equal(data, falseLiteral):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errors.New(\"JSON Error: malformed boolean value found while parsing boolean value\")\n\t}\n}\n\n// PaseFloatLiteral parses a float64 from the given byte slice.\n//\n// It utilizes double-precision (64-bit) floating-point format as defined\n// by the IEEE 754 standard, providing a decimal precision of approximately 15 digits.\nfunc ParseFloatLiteral(bytes []byte) (float64, error) {\n\tif len(bytes) == 0 {\n\t\treturn -1, errors.New(\"JSON Error: empty byte slice found while parsing float value\")\n\t}\n\n\tneg, bytes := trimNegativeSign(bytes)\n\n\tvar exponentPart []byte\n\tfor i, c := range bytes {\n\t\tif lower(c) == 'e' {\n\t\t\texponentPart = bytes[i+1:]\n\t\t\tbytes = bytes[:i]\n\t\t\tbreak\n\t\t}\n\t}\n\n\tman, exp10, err := extractMantissaAndExp10(bytes)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\tif len(exponentPart) \u003e 0 {\n\t\texp, err := strconv.Atoi(string(exponentPart))\n\t\tif err != nil {\n\t\t\treturn -1, errors.New(\"JSON Error: invalid exponent value found while parsing float value\")\n\t\t}\n\t\texp10 += exp\n\t}\n\n\t// for fast float64 conversion\n\tf, success := el.EiselLemire64(man, exp10, neg)\n\tif !success {\n\t\treturn 0, nil\n\t}\n\n\treturn f, nil\n}\n\nfunc ParseIntLiteral(bytes []byte) (int64, error) {\n\tif len(bytes) == 0 {\n\t\treturn 0, errors.New(\"JSON Error: empty byte slice found while parsing integer value\")\n\t}\n\n\tneg, bytes := trimNegativeSign(bytes)\n\n\tvar n uint64 = 0\n\tfor _, c := range bytes {\n\t\tif notDigit(c) {\n\t\t\treturn 0, errors.New(\"JSON Error: non-digit characters found while parsing integer value\")\n\t\t}\n\n\t\tif n \u003e maxUint64/10 {\n\t\t\treturn 0, errors.New(\"JSON Error: numeric value exceeds the range limit\")\n\t\t}\n\n\t\tn *= 10\n\n\t\tn1 := n + uint64(c-'0')\n\t\tif n1 \u003c n {\n\t\t\treturn 0, errors.New(\"JSON Error: numeric value exceeds the range limit\")\n\t\t}\n\n\t\tn = n1\n\t}\n\n\tif n \u003e maxInt64 {\n\t\tif neg \u0026\u0026 n == absMinInt64 {\n\t\t\treturn -absMinInt64, nil\n\t\t}\n\n\t\treturn 0, errors.New(\"JSON Error: numeric value exceeds the range limit\")\n\t}\n\n\tif neg {\n\t\treturn -int64(n), nil\n\t}\n\n\treturn int64(n), nil\n}\n\n// extractMantissaAndExp10 parses a byte slice representing a decimal number and extracts the mantissa and the exponent of its base-10 representation.\n// It iterates through the bytes, constructing the mantissa by treating each byte as a digit.\n// If a decimal point is encountered, the function keeps track of the position of the decimal point to calculate the exponent.\n// The function ensures that:\n// - The number contains at most one decimal point.\n// - All characters in the byte slice are digits or a single decimal point.\n// - The resulting mantissa does not overflow a uint64.\nfunc extractMantissaAndExp10(bytes []byte) (uint64, int, error) {\n\tvar (\n\t\tman uint64\n\t\texp10 int\n\t\tdecimalFound bool\n\t)\n\n\tfor _, c := range bytes {\n\t\tif c == dot {\n\t\t\tif decimalFound {\n\t\t\t\treturn 0, 0, errors.New(\"JSON Error: multiple decimal points found while parsing float value\")\n\t\t\t}\n\t\t\tdecimalFound = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif notDigit(c) {\n\t\t\treturn 0, 0, errors.New(\"JSON Error: non-digit characters found while parsing integer value\")\n\t\t}\n\n\t\tdigit := uint64(c - '0')\n\n\t\tif man \u003e (maxUint64-digit)/10 {\n\t\t\treturn 0, 0, errors.New(\"JSON Error: numeric value exceeds the range limit\")\n\t\t}\n\n\t\tman = man*10 + digit\n\n\t\tif decimalFound {\n\t\t\texp10--\n\t\t}\n\t}\n\n\treturn man, exp10, nil\n}\n\nfunc trimNegativeSign(bytes []byte) (bool, []byte) {\n\tif bytes[0] == minus {\n\t\treturn true, bytes[1:]\n\t}\n\n\treturn false, bytes\n}\n\nfunc notDigit(c byte) bool {\n\treturn (c \u0026 0xF0) != 0x30\n}\n\n// lower converts a byte to lower case if it is an uppercase letter.\nfunc lower(c byte) byte {\n\treturn c | 0x20\n}\n" + }, + { + "name": "parser_test.gno", + "body": "package json\n\nimport \"testing\"\n\nfunc TestParseStringLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t\tisError bool\n\t}{\n\t\t{`\"Hello, World!\"`, \"\\\"Hello, World!\\\"\", false},\n\t\t{`\\uFF11`, \"\\uFF11\", false},\n\t\t{`\\uFFFF`, \"\\uFFFF\", false},\n\t\t{`true`, \"true\", false},\n\t\t{`false`, \"false\", false},\n\t\t{`\\uDF00`, \"\", true},\n\t}\n\n\tfor i, tt := range tests {\n\t\ts, err := ParseStringLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif s != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%s, but actual=%s\", i, tt.expected, s)\n\t\t}\n\t}\n}\n\nfunc TestParseBoolLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected bool\n\t\tisError bool\n\t}{\n\t\t{`true`, true, false},\n\t\t{`false`, false, false},\n\t\t{`TRUE`, false, true},\n\t\t{`FALSE`, false, true},\n\t\t{`foo`, false, true},\n\t\t{`\"true\"`, false, true},\n\t\t{`\"false\"`, false, true},\n\t}\n\n\tfor i, tt := range tests {\n\t\tb, err := ParseBoolLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif b != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%t, but actual=%t\", i, tt.expected, b)\n\t\t}\n\t}\n}\n\nfunc TestParseFloatLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected float64\n\t}{\n\t\t{\"123\", 123},\n\t\t{\"-123\", -123},\n\t\t{\"123.456\", 123.456},\n\t\t{\"-123.456\", -123.456},\n\t\t{\"12345678.1234567890\", 12345678.1234567890},\n\t\t{\"-12345678.09123456789\", -12345678.09123456789},\n\t\t{\"0.123\", 0.123},\n\t\t{\"-0.123\", -0.123},\n\t\t{\"\", -1},\n\t\t{\"abc\", -1},\n\t\t{\"123.45.6\", -1},\n\t\t{\"999999999999999999999\", -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tgot, _ := ParseFloatLiteral([]byte(tt.input))\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"ParseFloatLiteral(%s): got %v, want %v\", tt.input, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseFloatWithScientificNotation(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected float64\n\t}{\n\t\t{\"1e6\", 1000000},\n\t\t{\"1E6\", 1000000},\n\t\t{\"1.23e10\", 1.23e10},\n\t\t{\"1.23E10\", 1.23e10},\n\t\t{\"-1.23e10\", -1.23e10},\n\t\t{\"-1.23E10\", -1.23e10},\n\t\t{\"2.45e-8\", 2.45e-8},\n\t\t{\"2.45E-8\", 2.45e-8},\n\t\t{\"-2.45e-8\", -2.45e-8},\n\t\t{\"-2.45E-8\", -2.45e-8},\n\t\t{\"5e0\", 5},\n\t\t{\"-5e0\", -5},\n\t\t{\"5E+0\", 5},\n\t\t{\"5e+1\", 50},\n\t\t{\"1e-1\", 0.1},\n\t\t{\"1E-1\", 0.1},\n\t\t{\"-1e-1\", -0.1},\n\t\t{\"-1E-1\", -0.1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tgot, err := ParseFloatLiteral([]byte(tt.input))\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"ParseFloatLiteral(%s): got %v, want %v\", tt.input, got, tt.expected)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"ParseFloatLiteral(%s): got error %v\", tt.input, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseFloat_May_Interoperability_Problem(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tshouldErr bool\n\t}{\n\t\t{\"3.141592653589793238462643383279\", true},\n\t\t{\"1E400\", false}, // TODO: should error\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\t_, err := ParseFloatLiteral([]byte(tt.input))\n\t\t\tif tt.shouldErr \u0026\u0026 err == nil {\n\t\t\t\tt.Errorf(\"ParseFloatLiteral(%s): expected error, but not error\", tt.input)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseIntLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"-1\", -1},\n\t\t{\"12345\", 12345},\n\t\t{\"-12345\", -12345},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"-9223372036854775808\", -9223372036854775808},\n\t\t{\"-92233720368547758081\", 0},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"9223372036854775808\", 0},\n\t\t{\"-9223372036854775809\", 0},\n\t\t{\"\", 0},\n\t\t{\"abc\", 0},\n\t\t{\"12345x\", 0},\n\t\t{\"123e5\", 0},\n\t\t{\"9223372036854775807x\", 0},\n\t\t{\"27670116110564327410\", 0},\n\t\t{\"-27670116110564327410\", 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tgot, _ := ParseIntLiteral([]byte(tt.input))\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"ParseIntLiteral(%s): got %v, want %v\", tt.input, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n" + }, + { + "name": "path.gno", + "body": "package json\n\nimport (\n\t\"errors\"\n)\n\n// ParsePath takes a JSONPath string and returns a slice of strings representing the path segments.\nfunc ParsePath(path string) ([]string, error) {\n\tbuf := newBuffer([]byte(path))\n\tresult := make([]string, 0)\n\n\tfor {\n\t\tb, err := buf.current()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch {\n\t\tcase b == dollarSign || b == atSign:\n\t\t\tresult = append(result, string(b))\n\t\t\tbuf.step()\n\n\t\tcase b == dot:\n\t\t\tbuf.step()\n\n\t\t\tif next, _ := buf.current(); next == dot {\n\t\t\t\tbuf.step()\n\t\t\t\tresult = append(result, \"..\")\n\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t} else {\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t}\n\n\t\tcase b == bracketOpen:\n\t\t\tstart := buf.index\n\t\t\tbuf.step()\n\n\t\t\tfor {\n\t\t\t\tif buf.index \u003e= buf.length || buf.data[buf.index] == bracketClose {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tbuf.step()\n\t\t\t}\n\n\t\t\tif buf.index \u003e= buf.length {\n\t\t\t\treturn nil, errors.New(\"unexpected end of path\")\n\t\t\t}\n\n\t\t\tsegment := string(buf.sliceFromIndices(start+1, buf.index))\n\t\t\tresult = append(result, segment)\n\n\t\t\tbuf.step()\n\n\t\tdefault:\n\t\t\tbuf.step()\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// extractNextSegment extracts the segment from the current index\n// to the next significant character and adds it to the resulting slice.\nfunc extractNextSegment(buf *buffer, result *[]string) {\n\tstart := buf.index\n\tbuf.skipToNextSignificantToken()\n\n\tif buf.index \u003c= start {\n\t\treturn\n\t}\n\n\tsegment := string(buf.sliceFromIndices(start, buf.index))\n\tif segment != \"\" {\n\t\t*result = append(*result, segment)\n\t}\n}\n" + }, + { + "name": "path_test.gno", + "body": "package json\n\nimport \"testing\"\n\nfunc TestParseJSONPath(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\texpected []string\n\t}{\n\t\t{name: \"Empty string path\", path: \"\", expected: []string{}},\n\t\t{name: \"Root only path\", path: \"$\", expected: []string{\"$\"}},\n\t\t{name: \"Root with dot path\", path: \"$.\", expected: []string{\"$\"}},\n\t\t{name: \"All objects in path\", path: \"$..\", expected: []string{\"$\", \"..\"}},\n\t\t{name: \"Only children in path\", path: \"$.*\", expected: []string{\"$\", \"*\"}},\n\t\t{name: \"All objects' children in path\", path: \"$..*\", expected: []string{\"$\", \"..\", \"*\"}},\n\t\t{name: \"Simple dot notation path\", path: \"$.root.element\", expected: []string{\"$\", \"root\", \"element\"}},\n\t\t{name: \"Complex dot notation path with wildcard\", path: \"$.root.*.element\", expected: []string{\"$\", \"root\", \"*\", \"element\"}},\n\t\t{name: \"Path with array wildcard\", path: \"$.phoneNumbers[*].type\", expected: []string{\"$\", \"phoneNumbers\", \"*\", \"type\"}},\n\t\t{name: \"Path with filter expression\", path: \"$.store.book[?(@.price \u003c 10)].title\", expected: []string{\"$\", \"store\", \"book\", \"?(@.price \u003c 10)\", \"title\"}},\n\t\t{name: \"Path with formula\", path: \"$..phoneNumbers..('ty' + 'pe')\", expected: []string{\"$\", \"..\", \"phoneNumbers\", \"..\", \"('ty' + 'pe')\"}},\n\t\t{name: \"Simple bracket notation path\", path: \"$['root']['element']\", expected: []string{\"$\", \"'root'\", \"'element'\"}},\n\t\t{name: \"Complex bracket notation path with wildcard\", path: \"$['root'][*]['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Bracket notation path with integer index\", path: \"$['store']['book'][0]['title']\", expected: []string{\"$\", \"'store'\", \"'book'\", \"0\", \"'title'\"}},\n\t\t{name: \"Complex path with wildcard in bracket notation\", path: \"$['root'].*['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot after bracket\", path: \"$.['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot before bracket\", path: \"$['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Single character path with root\", path: \"$.a\", expected: []string{\"$\", \"a\"}},\n\t\t{name: \"Multiple characters path with root\", path: \"$.abc\", expected: []string{\"$\", \"abc\"}},\n\t\t{name: \"Multiple segments path with root\", path: \"$.a.b.c\", expected: []string{\"$\", \"a\", \"b\", \"c\"}},\n\t\t{name: \"Multiple segments path with wildcard and root\", path: \"$.a.*.c\", expected: []string{\"$\", \"a\", \"*\", \"c\"}},\n\t\t{name: \"Multiple segments path with filter and root\", path: \"$.a[?(@.b == 'c')].d\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\"}},\n\t\t{name: \"Complex path with multiple filters\", path: \"$.a[?(@.b == 'c')].d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Complex path with multiple filters and wildcards\", path: \"$.a[?(@.b == 'c')].*.d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"*\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Path with array index and root\", path: \"$.a[0].b\", expected: []string{\"$\", \"a\", \"0\", \"b\"}},\n\t\t{name: \"Path with multiple array indices and root\", path: \"$.a[0].b[1].c\", expected: []string{\"$\", \"a\", \"0\", \"b\", \"1\", \"c\"}},\n\t\t{name: \"Path with array index, wildcard and root\", path: \"$.a[0].*.c\", expected: []string{\"$\", \"a\", \"0\", \"*\", \"c\"}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treult, _ := ParsePath(tt.path)\n\t\t\tif !isEqualSlice(reult, tt.expected) {\n\t\t\t\tt.Errorf(\"ParsePath(%s) expected: %v, got: %v\", tt.path, tt.expected, reult)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc isEqualSlice(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n" + }, + { + "name": "token.gno", + "body": "package json\n\nconst (\n\tbracketOpen = '['\n\tbracketClose = ']'\n\tparenOpen = '('\n\tparenClose = ')'\n\tcurlyOpen = '{'\n\tcurlyClose = '}'\n\tcomma = ','\n\tdot = '.'\n\tcolon = ':'\n\tbackTick = '`'\n\tsingleQuote = '\\''\n\tdoubleQuote = '\"'\n\temptyString = \"\"\n\twhiteSpace = ' '\n\tplus = '+'\n\tminus = '-'\n\taesterisk = '*'\n\tbang = '!'\n\tquestion = '?'\n\tnewLine = '\\n'\n\ttab = '\\t'\n\tcarriageReturn = '\\r'\n\tformFeed = '\\f'\n\tbackSpace = '\\b'\n\tslash = '/'\n\tbackSlash = '\\\\'\n\tunderScore = '_'\n\tdollarSign = '$'\n\tatSign = '@'\n\tandSign = '\u0026'\n\torSign = '|'\n)\n\nvar (\n\ttrueLiteral = []byte(\"true\")\n\tfalseLiteral = []byte(\"false\")\n\tnullLiteral = []byte(\"null\")\n)\n\ntype ValueType int\n\nconst (\n\tNotExist ValueType = iota\n\tString\n\tNumber\n\tFloat\n\tObject\n\tArray\n\tBoolean\n\tNull\n\tUnknown\n)\n\nfunc (v ValueType) String() string {\n\tswitch v {\n\tcase NotExist:\n\t\treturn \"not-exist\"\n\tcase String:\n\t\treturn \"string\"\n\tcase Number:\n\t\treturn \"number\"\n\tcase Object:\n\t\treturn \"object\"\n\tcase Array:\n\t\treturn \"array\"\n\tcase Boolean:\n\t\treturn \"boolean\"\n\tcase Null:\n\t\treturn \"null\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "int32", + "path": "gno.land/p/demo/math_eval/int32", + "files": [ + { + "name": "int32.gno", + "body": "// eval/int32 is a evaluator for int32 expressions.\n// This code is heavily forked from https://github.com/dengsgo/math-engine\n// which is licensed under Apache 2.0:\n// https://raw.githubusercontent.com/dengsgo/math-engine/298e2b57b7e7350d0f67bd036916efd5709abe25/LICENSE\npackage int32\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tIdentifier = iota\n\tNumber // numbers\n\tOperator // +, -, *, /, etc.\n\tVariable // x, y, z, etc. (one-letter only)\n)\n\ntype expression interface {\n\tString() string\n}\n\ntype expressionRaw struct {\n\texpression string\n\tType int\n\tFlag int\n\tOffset int\n}\n\ntype parser struct {\n\tInput string\n\tch byte\n\toffset int\n\terr error\n}\n\ntype expressionNumber struct {\n\tVal int\n\tStr string\n}\n\ntype expressionVariable struct {\n\tVal int\n\tStr string\n}\n\ntype expressionOperation struct {\n\tOp string\n\tLhs,\n\tRhs expression\n}\n\ntype ast struct {\n\trawexpressions []*expressionRaw\n\tsource string\n\tcurrentexpression *expressionRaw\n\tcurrentIndex int\n\tdepth int\n\terr error\n}\n\n// Parse takes an expression string, e.g. \"1+2\" and returns\n// a parsed expression. If there is an error it will return.\nfunc Parse(s string) (ar expression, err error) {\n\ttoks, err := lexer(s)\n\tif err != nil {\n\t\treturn\n\t}\n\tast, err := newAST(toks, s)\n\tif err != nil {\n\t\treturn\n\t}\n\tar, err = ast.parseExpression()\n\treturn\n}\n\n// Eval takes a parsed expression and a map of variables (or nil). The parsed\n// expression is evaluated using any variables and returns the\n// resulting int and/or error.\nfunc Eval(expr expression, variables map[string]int) (res int, err error) {\n\tif err != nil {\n\t\treturn\n\t}\n\tvar l, r int\n\tswitch expr.(type) {\n\tcase expressionVariable:\n\t\tast := expr.(expressionVariable)\n\t\tok := false\n\t\tif variables != nil {\n\t\t\tres, ok = variables[ast.Str]\n\t\t}\n\t\tif !ok {\n\t\t\terr = ufmt.Errorf(\"variable '%s' not found\", ast.Str)\n\t\t}\n\t\treturn\n\tcase expressionOperation:\n\t\tast := expr.(expressionOperation)\n\t\tl, err = Eval(ast.Lhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr, err = Eval(ast.Rhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch ast.Op {\n\t\tcase \"+\":\n\t\t\tres = l + r\n\t\tcase \"-\":\n\t\t\tres = l - r\n\t\tcase \"*\":\n\t\t\tres = l * r\n\t\tcase \"/\":\n\t\t\tif r == 0 {\n\t\t\t\terr = ufmt.Errorf(\"violation of arithmetic specification: a division by zero in Eval: [%d/%d]\", l, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tres = l / r\n\t\tcase \"%\":\n\t\t\tif r == 0 {\n\t\t\t\tres = 0\n\t\t\t} else {\n\t\t\t\tres = l % r\n\t\t\t}\n\t\tcase \"^\":\n\t\t\tres = l ^ r\n\t\tcase \"\u003e\u003e\":\n\t\t\tres = l \u003e\u003e r\n\t\tcase \"\u003c\u003c\":\n\t\t\tres = l \u003c\u003c r\n\t\tcase \"\u003e\":\n\t\t\tif l \u003e r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u003c\":\n\t\t\tif l \u003c r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u0026\":\n\t\t\tres = l \u0026 r\n\t\tcase \"|\":\n\t\t\tres = l | r\n\t\tdefault:\n\n\t\t}\n\tcase expressionNumber:\n\t\tres = expr.(expressionNumber).Val\n\t}\n\n\treturn\n}\n\nfunc expressionError(s string, pos int) string {\n\tr := strings.Repeat(\"-\", len(s)) + \"\\n\"\n\ts += \"\\n\"\n\tfor i := 0; i \u003c pos; i++ {\n\t\ts += \" \"\n\t}\n\ts += \"^\\n\"\n\treturn r + s + r\n}\n\nfunc (n expressionVariable) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionVariable: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (n expressionNumber) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionNumber: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (b expressionOperation) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionOperation: (%s %s %s)\",\n\t\tb.Op,\n\t\tb.Lhs.String(),\n\t\tb.Rhs.String(),\n\t)\n}\n\nfunc newAST(toks []*expressionRaw, s string) (*ast, error) {\n\ta := \u0026ast{\n\t\trawexpressions: toks,\n\t\tsource: s,\n\t}\n\tif a.rawexpressions == nil || len(a.rawexpressions) == 0 {\n\t\treturn a, errors.New(\"empty token\")\n\t} else {\n\t\ta.currentIndex = 0\n\t\ta.currentexpression = a.rawexpressions[0]\n\t}\n\treturn a, nil\n}\n\nfunc (a *ast) parseExpression() (expression, error) {\n\ta.depth++ // called depth\n\tlhs := a.parsePrimary()\n\tr := a.parseBinOpRHS(0, lhs)\n\ta.depth--\n\tif a.depth == 0 \u0026\u0026 a.currentIndex != len(a.rawexpressions) \u0026\u0026 a.err == nil {\n\t\treturn r, ufmt.Errorf(\"bad expression, reaching the end or missing the operator\\n%s\",\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t}\n\treturn r, nil\n}\n\nfunc (a *ast) getNextexpressionRaw() *expressionRaw {\n\ta.currentIndex++\n\tif a.currentIndex \u003c len(a.rawexpressions) {\n\t\ta.currentexpression = a.rawexpressions[a.currentIndex]\n\t\treturn a.currentexpression\n\t}\n\treturn nil\n}\n\nfunc (a *ast) getTokPrecedence() int {\n\tswitch a.currentexpression.expression {\n\tcase \"/\", \"%\", \"*\":\n\t\treturn 100\n\tcase \"\u003c\u003c\", \"\u003e\u003e\":\n\t\treturn 80\n\tcase \"+\", \"-\":\n\t\treturn 75\n\tcase \"\u003c\", \"\u003e\":\n\t\treturn 70\n\tcase \"\u0026\":\n\t\treturn 60\n\tcase \"^\":\n\t\treturn 50\n\tcase \"|\":\n\t\treturn 40\n\t}\n\treturn -1\n}\n\nfunc (a *ast) parseNumber() expressionNumber {\n\tf64, err := strconv.Atoi(a.currentexpression.expression)\n\tif err != nil {\n\t\ta.err = ufmt.Errorf(\"%v\\nwant '(' or '0-9' but get '%s'\\n%s\",\n\t\t\terr.Error(),\n\t\t\ta.currentexpression.expression,\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\treturn expressionNumber{}\n\t}\n\tn := expressionNumber{\n\t\tVal: f64,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parseVariable() expressionVariable {\n\tn := expressionVariable{\n\t\tVal: 0,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parsePrimary() expression {\n\tswitch a.currentexpression.Type {\n\tcase Variable:\n\t\treturn a.parseVariable()\n\tcase Number:\n\t\treturn a.parseNumber()\n\tcase Operator:\n\t\tif a.currentexpression.expression == \"(\" {\n\t\t\tt := a.getNextexpressionRaw()\n\t\t\tif t == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\te, _ := a.parseExpression()\n\t\t\tif e == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif a.currentexpression.expression != \")\" {\n\t\t\t\ta.err = ufmt.Errorf(\"want ')' but get %s\\n%s\",\n\t\t\t\t\ta.currentexpression.expression,\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ta.getNextexpressionRaw()\n\t\t\treturn e\n\t\t} else if a.currentexpression.expression == \"-\" {\n\t\t\tif a.getNextexpressionRaw() == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '0-9' but get '-'\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbin := expressionOperation{\n\t\t\t\tOp: \"-\",\n\t\t\t\tLhs: expressionNumber{},\n\t\t\t\tRhs: a.parsePrimary(),\n\t\t\t}\n\t\t\treturn bin\n\t\t} else {\n\t\t\treturn a.parseNumber()\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (a *ast) parseBinOpRHS(execPrec int, lhs expression) expression {\n\tfor {\n\t\ttokPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c execPrec {\n\t\t\treturn lhs\n\t\t}\n\t\tbinOp := a.currentexpression.expression\n\t\tif a.getNextexpressionRaw() == nil {\n\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\treturn nil\n\t\t}\n\t\trhs := a.parsePrimary()\n\t\tif rhs == nil {\n\t\t\treturn nil\n\t\t}\n\t\tnextPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c nextPrec {\n\t\t\trhs = a.parseBinOpRHS(tokPrec+1, rhs)\n\t\t\tif rhs == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tlhs = expressionOperation{\n\t\t\tOp: binOp,\n\t\t\tLhs: lhs,\n\t\t\tRhs: rhs,\n\t\t}\n\t}\n}\n\nfunc lexer(s string) ([]*expressionRaw, error) {\n\tp := \u0026parser{\n\t\tInput: s,\n\t\terr: nil,\n\t\tch: s[0],\n\t}\n\ttoks := p.parse()\n\tif p.err != nil {\n\t\treturn nil, p.err\n\t}\n\treturn toks, nil\n}\n\nfunc (p *parser) parse() []*expressionRaw {\n\ttoks := make([]*expressionRaw, 0)\n\tfor {\n\t\ttok := p.nextTok()\n\t\tif tok == nil {\n\t\t\tbreak\n\t\t}\n\t\ttoks = append(toks, tok)\n\t}\n\treturn toks\n}\n\nfunc (p *parser) nextTok() *expressionRaw {\n\tif p.offset \u003e= len(p.Input) || p.err != nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tfor p.isWhitespace(p.ch) \u0026\u0026 err == nil {\n\t\terr = p.nextCh()\n\t}\n\tstart := p.offset\n\tvar tok *expressionRaw\n\tswitch p.ch {\n\tcase\n\t\t'(',\n\t\t')',\n\t\t'+',\n\t\t'-',\n\t\t'*',\n\t\t'/',\n\t\t'^',\n\t\t'\u0026',\n\t\t'|',\n\t\t'%':\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: string(p.ch),\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\terr = p.nextCh()\n\tcase '\u003e', '\u003c':\n\t\ttokS := string(p.ch)\n\t\tbb, be := p.nextChPeek()\n\t\tif be == nil \u0026\u0026 string(bb) == tokS {\n\t\t\ttokS += string(p.ch)\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: tokS,\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\tif len(tokS) \u003e 1 {\n\t\t\tp.nextCh()\n\t\t}\n\t\terr = p.nextCh()\n\tcase\n\t\t'0',\n\t\t'1',\n\t\t'2',\n\t\t'3',\n\t\t'4',\n\t\t'5',\n\t\t'6',\n\t\t'7',\n\t\t'8',\n\t\t'9':\n\t\tfor p.isDigitNum(p.ch) \u0026\u0026 p.nextCh() == nil {\n\t\t\tif (p.ch == '-' || p.ch == '+') \u0026\u0026 p.Input[p.offset-1] != 'e' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: strings.ReplaceAll(p.Input[start:p.offset], \"_\", \"\"),\n\t\t\tType: Number,\n\t\t}\n\t\ttok.Offset = start\n\tdefault:\n\t\tif p.isChar(p.ch) {\n\t\t\ttok = \u0026expressionRaw{\n\t\t\t\texpression: string(p.ch),\n\t\t\t\tType: Variable,\n\t\t\t}\n\t\t\ttok.Offset = start\n\t\t\terr = p.nextCh()\n\t\t} else if p.ch != ' ' {\n\t\t\tp.err = ufmt.Errorf(\"symbol error: unknown '%v', pos [%v:]\\n%s\",\n\t\t\t\tstring(p.ch),\n\t\t\t\tstart,\n\t\t\t\texpressionError(p.Input, start))\n\t\t}\n\t}\n\treturn tok\n}\n\nfunc (p *parser) nextChPeek() (byte, error) {\n\toffset := p.offset + 1\n\tif offset \u003c len(p.Input) {\n\t\treturn p.Input[offset], nil\n\t}\n\treturn byte(0), errors.New(\"no byte\")\n}\n\nfunc (p *parser) nextCh() error {\n\tp.offset++\n\tif p.offset \u003c len(p.Input) {\n\t\tp.ch = p.Input[p.offset]\n\t\treturn nil\n\t}\n\treturn errors.New(\"EOF\")\n}\n\nfunc (p *parser) isWhitespace(c byte) bool {\n\treturn c == ' ' ||\n\t\tc == '\\t' ||\n\t\tc == '\\n' ||\n\t\tc == '\\v' ||\n\t\tc == '\\f' ||\n\t\tc == '\\r'\n}\n\nfunc (p *parser) isDigitNum(c byte) bool {\n\treturn '0' \u003c= c \u0026\u0026 c \u003c= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'\n}\n\nfunc (p *parser) isChar(c byte) bool {\n\treturn 'a' \u003c= c \u0026\u0026 c \u003c= 'z' || 'A' \u003c= c \u0026\u0026 c \u003c= 'Z'\n}\n\nfunc (p *parser) isWordChar(c byte) bool {\n\treturn p.isChar(c) || '0' \u003c= c \u0026\u0026 c \u003c= '9'\n}\n" + }, + { + "name": "int32_test.gno", + "body": "package int32\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) {\n\tttt := []struct {\n\t\texp string\n\t\tres int\n\t}{\n\t\t{\"1\", 1},\n\t\t{\"--1\", 1},\n\t\t{\"1+2\", 3},\n\t\t{\"-1+2\", 1},\n\t\t{\"-(1+2)\", -3},\n\t\t{\"-(1+2)*5\", -15},\n\t\t{\"-(1+2)*5/3\", -5},\n\t\t{\"1+(-(1+2)*5/3)\", -4},\n\t\t{\"3^4\", 3 ^ 4},\n\t\t{\"8%2\", 8 % 2},\n\t\t{\"8%3\", 8 % 3},\n\t\t{\"8|3\", 8 | 3},\n\t\t{\"10%2\", 0},\n\t\t{\"(4 + 3)/2-1+11*15\", (4+3)/2 - 1 + 11*15},\n\t\t{\n\t\t\t\"(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64)\",\n\t\t\t(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64),\n\t\t},\n\t\t{\n\t\t\t\"(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64)\",\n\t\t\t(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64),\n\t\t},\n\t\t{\"((0000+1)*0000)\", 0},\n\t}\n\tfor _, tc := range ttt {\n\t\tt.Run(tc.exp, func(t *testing.T) {\n\t\t\texp, err := Parse(tc.exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"%s:\\n%s\", tc.exp, err.Error())\n\t\t\t} else {\n\t\t\t\tres, errEval := Eval(exp, nil)\n\t\t\t\tif errEval != nil {\n\t\t\t\t\tt.Errorf(\"eval error: %s\", errEval.Error())\n\t\t\t\t} else if res != tc.res {\n\t\t\t\t\tt.Errorf(\"%s:\\nexpected %d, got %d\", tc.exp, tc.res, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVariables(t *testing.T) {\n\tfn := func(x, y int) int {\n\t\treturn 1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\n\t}\n\texpr := \"1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\"\n\texp, err := Parse(expr)\n\tif err != nil {\n\t\tt.Errorf(\"could not parse: %s\", err.Error())\n\t}\n\tvariables := make(map[string]int)\n\tfor i := 0; i \u003c 10; i++ {\n\t\tvariables[\"x\"] = i\n\t\tvariables[\"y\"] = 2\n\t\tres, errEval := Eval(exp, variables)\n\t\tif errEval != nil {\n\t\t\tt.Errorf(\"could not evaluate: %s\", err.Error())\n\t\t}\n\t\texpected := fn(variables[\"x\"], variables[\"y\"])\n\t\tif res != expected {\n\t\t\tt.Errorf(\"expected: %d, actual: %d\", expected, res)\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ownable", + "path": "gno.land/p/demo/ownable", + "files": [ + { + "name": "errors.gno", + "body": "package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"unauthorized; caller is not owner\")\n\tErrInvalidAddress = errors.New(\"new owner address is invalid\")\n)\n" + }, + { + "name": "ownable.gno", + "body": "package ownable\n\nimport (\n\t\"std\"\n)\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PrevRealm().Addr(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{owner: addr}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PrevRealm().Addr() == o.owner {\n\t\treturn nil\n\t}\n\treturn ErrUnauthorized\n}\n\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PrevRealm().Addr() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n" + }, + { + "name": "ownable_test.gno", + "body": "package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tfirstCaller = testutils.TestAddress(\"first\")\n\tsecondCaller = testutils.TestAddress(\"second\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\tstd.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tuassert.Equal(t, firstCaller, got)\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(firstCaller)\n\n\tgot := o.Owner()\n\tuassert.Equal(t, firstCaller, got)\n}\n\nfunc TestOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\n\to := New()\n\texpected := firstCaller\n\tgot := o.Owner()\n\tuassert.Equal(t, expected, got)\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\n\to := New()\n\n\terr := o.TransferOwnership(secondCaller)\n\tuassert.NoError(t, err, \"TransferOwnership failed\")\n\n\tgot := o.Owner()\n\tuassert.Equal(t, secondCaller, got)\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\n\to := New()\n\tunauthorizedCaller := secondCaller\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\tstd.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(secondCaller))\n\tstd.TestSetOrigCaller(secondCaller) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(firstCaller)\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(firstCaller))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "seqid", + "path": "gno.land/p/demo/seqid", + "files": [ + { + "name": "README.md", + "body": "# seqid\n\n```\npackage seqid // import \"gno.land/p/demo/seqid\"\n\nPackage seqid provides a simple way to have sequential IDs which will be ordered\ncorrectly when inserted in an AVL tree.\n\nSample usage:\n\n var id seqid.ID\n var users avl.Tree\n\n func NewUser() {\n \tusers.Set(id.Next().Binary(), \u0026User{ ... })\n }\n\nTYPES\n\ntype ID uint64\n An ID is a simple sequential ID generator.\n\nfunc FromBinary(b string) (ID, bool)\n FromBinary creates a new ID from the given string.\n\nfunc (i ID) Binary() string\n Binary returns a big-endian binary representation of the ID, suitable to be\n used as an AVL key.\n\nfunc (i *ID) Next() ID\n Next advances the ID i. It will panic if increasing ID would overflow.\n\nfunc (i *ID) TryNext() (ID, bool)\n TryNext increases i by 1 and returns its value. It returns true if\n successful, or false if the increment would result in an overflow.\n```\n" + }, + { + "name": "seqid.gno", + "body": "// Package seqid provides a simple way to have sequential IDs which will be\n// ordered correctly when inserted in an AVL tree.\n//\n// Sample usage:\n//\n//\tvar id seqid.ID\n//\tvar users avl.Tree\n//\n//\tfunc NewUser() {\n//\t\tusers.Set(id.Next().String(), \u0026User{ ... })\n//\t}\npackage seqid\n\nimport (\n\t\"encoding/binary\"\n\n\t\"gno.land/p/demo/cford32\"\n)\n\n// An ID is a simple sequential ID generator.\ntype ID uint64\n\n// Next advances the ID i.\n// It will panic if increasing ID would overflow.\nfunc (i *ID) Next() ID {\n\tnext, ok := i.TryNext()\n\tif !ok {\n\t\tpanic(\"seqid: next ID overflows uint64\")\n\t}\n\treturn next\n}\n\nconst maxID ID = 1\u003c\u003c64 - 1\n\n// TryNext increases i by 1 and returns its value.\n// It returns true if successful, or false if the increment would result in\n// an overflow.\nfunc (i *ID) TryNext() (ID, bool) {\n\tif *i == maxID {\n\t\t// Addition will overflow.\n\t\treturn 0, false\n\t}\n\t*i++\n\treturn *i, true\n}\n\n// Binary returns a big-endian binary representation of the ID,\n// suitable to be used as an AVL key.\nfunc (i ID) Binary() string {\n\tbuf := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(buf, uint64(i))\n\treturn string(buf)\n}\n\n// String encodes i using cford32's compact encoding. For more information,\n// see the documentation for package [gno.land/p/demo/cford32].\n//\n// The result of String will be a 7-byte string for IDs [0,2^34), and a\n// 13-byte string for all values following that. All generated string IDs\n// follow the same lexicographic order as their number values; that is, for any\n// two IDs (x, y) such that x \u003c y, x.String() \u003c y.String().\n// As such, this string representation is suitable to be used as an AVL key.\nfunc (i ID) String() string {\n\treturn string(cford32.PutCompact(uint64(i)))\n}\n\n// FromBinary creates a new ID from the given string, expected to be a binary\n// big-endian encoding of an ID (such as that of [ID.Binary]).\n// The second return value is true if the conversion was successful.\nfunc FromBinary(b string) (ID, bool) {\n\tif len(b) != 8 {\n\t\treturn 0, false\n\t}\n\treturn ID(binary.BigEndian.Uint64([]byte(b))), true\n}\n\n// FromString creates a new ID from the given string, expected to be a string\n// representation using cford32, such as that returned by [ID.String].\n//\n// The encoding scheme used by cford32 allows the same ID to have many\n// different representations (though the one returned by [ID.String] is only\n// one, deterministic and safe to be used in AVL). The encoding scheme is\n// \"human-centric\" and is thus case insensitive, and maps some ambiguous\n// characters to be the same, ie. L = I = 1, O = 0. For this reason, when\n// parsing user input to retrieve a key (encoded as a string), always sanitize\n// it first using FromString, then run String(), instead of using the user's\n// input directly.\nfunc FromString(b string) (ID, error) {\n\tn, err := cford32.Uint64([]byte(b))\n\treturn ID(n), err\n}\n" + }, + { + "name": "seqid_test.gno", + "body": "package seqid\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestID(t *testing.T) {\n\tvar i ID\n\n\tfor j := 0; j \u003c 100; j++ {\n\t\ti.Next()\n\t}\n\tif i != 100 {\n\t\tt.Fatalf(\"invalid: wanted %d got %d\", 100, i)\n\t}\n}\n\nfunc TestID_Overflow(t *testing.T) {\n\ti := ID(maxID)\n\n\tdefer func() {\n\t\terr := recover()\n\t\tif !strings.Contains(fmt.Sprint(err), \"next ID overflows\") {\n\t\t\tt.Errorf(\"did not overflow\")\n\t\t}\n\t}()\n\n\ti.Next()\n}\n\nfunc TestID_Binary(t *testing.T) {\n\tvar i ID\n\tprev := i.Binary()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().Binary()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %x \u003e prev %x\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n}\n\nfunc TestID_String(t *testing.T) {\n\tvar i ID\n\tprev := i.String()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().String()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n\n\t// Test for when cford32 switches over to the long encoding.\n\ti = 1\u003c\u003c34 - 512\n\tfor j := 0; j \u003c 1024; j++ {\n\t\tcur := i.Next().String()\n\t\t// println(cur)\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "memeland", + "path": "gno.land/p/demo/memeland", + "files": [ + { + "name": "memeland.gno", + "body": "package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PrevRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PrevRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n" + }, + { + "name": "memeland_test.gno", + "body": "package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tif id == \"\" {\n\t\tt.Error(\"Expected valid ID, got empty string\")\n\t}\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tif postCount != tc.expectedNumOfPosts {\n\t\t\t\tt.Errorf(\"Expected %d posts in the JSON string, but found %d\", tc.expectedNumOfPosts, postCount)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tif jsonStr == \"\" {\n\t\tt.Error(\"Expected non-empty JSON string, got empty string\")\n\t}\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tif seqid.ID(postCount) != m.MemeCounter {\n\t\tt.Errorf(\"Expected %d posts in the JSON string, but found %d\", m.MemeCounter, postCount)\n\t}\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tif !strings.Contains(jsonStr, expData) {\n\t\t\tt.Errorf(\"Expected %s in the JSON string, but counld't find it\", expData)\n\t\t}\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tif strings.Index(jsonStr, memeData[i]) \u003c strings.Index(jsonStr, memeData[i+1]) {\n\t\t\tt.Errorf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1)\n\t\t}\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOrigCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tif jsonStr == \"\" {\n\t\tt.Error(\"Expected non-empty JSON string, got empty string\")\n\t}\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tif seqid.ID(postCount) != m.MemeCounter {\n\t\tt.Errorf(\"Expected %d posts in the JSON string, but found %d\", m.MemeCounter, postCount)\n\t}\n\n\t// Check if ordering is correct\n\tif strings.Index(jsonStr, \"Meme #1\") \u003e strings.Index(jsonStr, \"Meme #2\") {\n\t\tt.Errorf(\"Expected %s to be before %s\", memeData1, memeData2)\n\t}\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tif jsonStr != \"[]\" {\n\t\tt.Errorf(\"Expected 0 posts to return [], got %s\", jsonStr)\n\t}\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\n\tif post.UpvoteTracker.Size() != 0 {\n\t\tt.Errorf(\"Expected initial upvotes to be 0, got %d\", post.UpvoteTracker.Size())\n\t}\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tif upvoteResult != \"upvote successful\" {\n\t\tt.Errorf(\"Expected upvote to be successful, got: %s\", upvoteResult)\n\t}\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\n\tif post.UpvoteTracker.Size() != 1 {\n\t\tt.Errorf(\"Expected upvotes to be 1 after upvoting, got %d\", post.UpvoteTracker.Size())\n\t}\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOrigCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOrigCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOrigCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tif id != postID {\n\t\tt.Errorf(\"post IDs not matching\")\n\t}\n\n\tif len(m.Posts) != 0 {\n\t\tt.Errorf(\"there should be 0 posts after removing\")\n\t}\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOrigCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOrigCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "merkle", + "path": "gno.land/p/demo/merkle", + "files": [ + { + "name": "README.md", + "body": "# p/demo/merkle\n\nThis package implement a merkle tree that is complient with [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n## [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n```javascript\nconst { MerkleTree } = require(\"merkletreejs\");\nconst SHA256 = require(\"crypto-js/sha256\");\n\nlet leaves = [];\nfor (let i = 0; i \u003c 10; i++) {\n leaves.push(SHA256(`node_${i}`));\n}\n\nconst tree = new MerkleTree(leaves, SHA256);\nconst root = tree.getRoot().toString(\"hex\");\n\nconsole.log(root); // cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\n```\n" + }, + { + "name": "merkle.gno", + "body": "package merkle\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n)\n\ntype Hashable interface {\n\tBytes() []byte\n}\n\ntype nodes []Node\n\ntype Node struct {\n\thash []byte\n\n\tposition uint8\n}\n\nfunc (n Node) Hash() string {\n\treturn hex.EncodeToString(n.hash[:])\n}\n\ntype Tree struct {\n\tlayers []nodes\n}\n\n// Root return the merkle root of the tree\nfunc (t *Tree) Root() string {\n\tfor _, l := range t.layers {\n\t\tif len(l) == 1 {\n\t\t\treturn l[0].Hash()\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// NewTree create a new Merkle Tree\nfunc NewTree(data []Hashable) *Tree {\n\ttree := \u0026Tree{}\n\n\tleaves := make([]Node, len(data))\n\n\tfor i, d := range data {\n\t\thash := sha256.Sum256(d.Bytes())\n\t\tleaves[i] = Node{hash: hash[:]}\n\t}\n\n\ttree.layers = []nodes{nodes(leaves)}\n\n\tvar buff bytes.Buffer\n\tfor len(leaves) \u003e 1 {\n\t\tlevel := make([]Node, 0, len(leaves)/2+1)\n\t\tfor i := 0; i \u003c len(leaves); i += 2 {\n\t\t\tbuff.Reset()\n\n\t\t\tif i \u003c len(leaves)-1 {\n\t\t\t\tbuff.Write(leaves[i].hash)\n\t\t\t\tbuff.Write(leaves[i+1].hash)\n\t\t\t\thash := sha256.Sum256(buff.Bytes())\n\t\t\t\tlevel = append(level, Node{\n\t\t\t\t\thash: hash[:],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlevel = append(level, leaves[i])\n\t\t\t}\n\t\t}\n\t\tleaves = level\n\t\ttree.layers = append(tree.layers, level)\n\t}\n\treturn tree\n}\n\n// Proof return a MerkleProof\nfunc (t *Tree) Proof(data Hashable) ([]Node, error) {\n\ttargetHash := sha256.Sum256(data.Bytes())\n\ttargetIndex := -1\n\n\tfor i, layer := range t.layers[0] {\n\t\tif bytes.Equal(targetHash[:], layer.hash) {\n\t\t\ttargetIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetIndex == -1 {\n\t\treturn nil, errors.New(\"target not found\")\n\t}\n\n\tproofs := make([]Node, 0, len(t.layers))\n\n\tfor _, layer := range t.layers {\n\t\tvar pairIndex int\n\n\t\tif targetIndex%2 == 0 {\n\t\t\tpairIndex = targetIndex + 1\n\t\t} else {\n\t\t\tpairIndex = targetIndex - 1\n\t\t}\n\t\tif pairIndex \u003c len(layer) {\n\t\t\tproofs = append(proofs, Node{\n\t\t\t\thash: layer[pairIndex].hash,\n\t\t\t\tposition: uint8(targetIndex) % 2,\n\t\t\t})\n\t\t}\n\t\ttargetIndex /= 2\n\t}\n\treturn proofs, nil\n}\n\n// Verify if a merkle proof is valid\nfunc (t *Tree) Verify(leaf Hashable, proofs []Node) bool {\n\treturn Verify(t.Root(), leaf, proofs)\n}\n\n// Verify if a merkle proof is valid\nfunc Verify(root string, leaf Hashable, proofs []Node) bool {\n\thash := sha256.Sum256(leaf.Bytes())\n\n\tfor i := 0; i \u003c len(proofs); i += 1 {\n\t\tvar h []byte\n\t\tif proofs[i].position == 0 {\n\t\t\th = append(hash[:], proofs[i].hash...)\n\t\t} else {\n\t\t\th = append(proofs[i].hash, hash[:]...)\n\t\t}\n\t\thash = sha256.Sum256(h)\n\t}\n\treturn hex.EncodeToString(hash[:]) == root\n}\n" + }, + { + "name": "merkle_test.gno", + "body": "package merkle\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype testData struct {\n\tcontent string\n}\n\nfunc (d testData) Bytes() []byte {\n\treturn []byte(d.content)\n}\n\nfunc TestMerkleTree(t *testing.T) {\n\ttests := []struct {\n\t\tsize int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tsize: 1,\n\t\t\texpected: \"cf9f824bce7f5bc63d557b23591f58577f53fe29f974a615bdddbd0140f912f4\",\n\t\t},\n\t\t{\n\t\t\tsize: 3,\n\t\t\texpected: \"1a4a5f0fa267244bf9f74a63fdf2a87eed5e97e4bd104a9e94728c8fb5442177\",\n\t\t},\n\t\t{\n\t\t\tsize: 10,\n\t\t\texpected: \"cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\",\n\t\t},\n\t\t{\n\t\t\tsize: 1000,\n\t\t\texpected: \"fa533d2efdf12be26bc410dfa42936ac63361324e35e9b1ff54d422a1dd2388b\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tvar leaves []Hashable\n\t\tfor i := 0; i \u003c test.size; i++ {\n\t\t\tleaves = append(leaves, testData{fmt.Sprintf(\"node_%d\", i)})\n\t\t}\n\n\t\ttree := NewTree(leaves)\n\n\t\tif tree == nil {\n\t\t\tt.Error(\"Merkle tree creation failed\")\n\t\t}\n\n\t\troot := tree.Root()\n\n\t\tif root != test.expected {\n\t\t\tt.Fatalf(\"merkle.Tree.Root(), expected: %s; got: %s\", test.expected, root)\n\t\t}\n\n\t\tfor _, leaf := range leaves {\n\t\t\tproofs, err := tree.Proof(leaf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to proof leaf: %v, on tree: %v\", leaf, test)\n\t\t\t}\n\n\t\t\tok := Verify(root, leaf, proofs)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"failed to verify leaf: %v, on tree: %v\", leaf, tree)\n\t\t\t}\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "microblog", + "path": "gno.land/p/demo/microblog", + "files": [ + { + "name": "microblog.gno", + "body": "package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.GetOrigCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "pausable", + "path": "gno.land/p/demo/pausable", + "files": [ + { + "name": "pausable.gno", + "body": "package pausable\n\nimport \"gno.land/p/demo/ownable\"\n\ntype Pausable struct {\n\t*ownable.Ownable\n\tpaused bool\n}\n\n// New returns a new Pausable struct with non-paused state as default\nfunc New() *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable.New(),\n\t\tpaused: false,\n\t}\n}\n\n// NewFromOwnable is the same as New, but with a pre-existing top-level ownable\nfunc NewFromOwnable(ownable *ownable.Ownable) *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable,\n\t\tpaused: false,\n\t}\n}\n\n// IsPaused checks if Pausable is paused\nfunc (p Pausable) IsPaused() bool {\n\treturn p.paused\n}\n\n// Pause sets the state of Pausable to true, meaning all pausable functions are paused\nfunc (p *Pausable) Pause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = true\n\treturn nil\n}\n\n// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed\nfunc (p *Pausable) Unpause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = false\n\treturn nil\n}\n" + }, + { + "name": "pausable_test.gno", + "body": "package pausable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nvar (\n\tfirstCaller = std.Address(\"g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de\")\n\tsecondCaller = std.Address(\"g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\n\tif result.paused != false {\n\t\tt.Fatalf(\"Expected result to be unpaused, got %t\\n\", result.paused)\n\t}\n\n\tif result.Owner() != firstCaller {\n\t\tt.Fatalf(\"Expected %s, got %s\\n\", firstCaller, result.Owner())\n\t}\n}\n\nfunc TestNewFromOwnable(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\to := ownable.New()\n\n\tstd.TestSetOrigCaller(secondCaller)\n\tresult := NewFromOwnable(o)\n\n\tif result.Owner() != firstCaller {\n\t\tt.Fatalf(\"Expected %s, got %s\\n\", firstCaller, result.Owner())\n\t}\n}\n\nfunc TestSetUnpaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\tresult.Unpause()\n\n\tif result.IsPaused() {\n\t\tt.Fatalf(\"Expected result to be unpaused, got %t\\n\", result.IsPaused())\n\t}\n}\n\nfunc TestSetPaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\tresult.Pause()\n\n\tif !result.IsPaused() {\n\t\tt.Fatalf(\"Expected result to be paused, got %t\\n\", result.IsPaused())\n\t}\n}\n\nfunc TestIsPaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\n\tif result.IsPaused() {\n\t\tt.Fatalf(\"Expected result to be unpaused, got %t\\n\", result.IsPaused())\n\t}\n\n\tresult.Pause()\n\n\tif !result.IsPaused() {\n\t\tt.Fatalf(\"Expected result to be paused, got %t\\n\", result.IsPaused())\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "releases", + "path": "gno.land/p/demo/releases", + "files": [ + { + "name": "changelog.gno", + "body": "package releases\n\ntype changelog struct {\n\tname string\n\treleases []release\n}\n\nfunc NewChangelog(name string) *changelog {\n\treturn \u0026changelog{\n\t\tname: name,\n\t\treleases: make([]release, 0),\n\t}\n}\n\nfunc (c *changelog) NewRelease(version, url, notes string) {\n\tif latest := c.Latest(); latest != nil {\n\t\tlatest.isLatest = false\n\t}\n\n\trelease := release{\n\t\t// manual\n\t\tversion: version,\n\t\turl: url,\n\t\tnotes: notes,\n\n\t\t// internal\n\t\tchangelog: c,\n\t\tisLatest: true,\n\t}\n\n\tc.releases = append(c.releases, release)\n}\n\nfunc (c *changelog) Render(path string) string {\n\tif path == \"\" {\n\t\toutput := \"# \" + c.name + \"\\n\\n\"\n\t\tmax := len(c.releases) - 1\n\t\tmin := 0\n\t\tif max-min \u003e 10 {\n\t\t\tmin = max - 10\n\t\t}\n\t\tfor i := max; i \u003e= min; i-- {\n\t\t\trelease := c.releases[i]\n\t\t\toutput += release.Render()\n\t\t}\n\t\treturn output\n\t}\n\n\trelease := c.ByVersion(path)\n\tif release != nil {\n\t\treturn release.Render()\n\t}\n\n\treturn \"no such release\"\n}\n\nfunc (c *changelog) Latest() *release {\n\tif len(c.releases) \u003e 0 {\n\t\tpos := len(c.releases) - 1\n\t\treturn \u0026c.releases[pos]\n\t}\n\treturn nil\n}\n\nfunc (c *changelog) ByVersion(version string) *release {\n\tfor _, release := range c.releases {\n\t\tif release.version == version {\n\t\t\treturn \u0026release\n\t\t}\n\t}\n\treturn nil\n}\n" + }, + { + "name": "release.gno", + "body": "package releases\n\ntype release struct {\n\t// manual\n\tversion string\n\turl string\n\tnotes string\n\n\t// internal\n\tisLatest bool\n\tchangelog *changelog\n}\n\nfunc (r *release) URL() string { return r.url }\nfunc (r *release) Version() string { return r.version }\nfunc (r *release) Notes() string { return r.notes }\nfunc (r *release) IsLatest() bool { return r.isLatest }\n\nfunc (r *release) Title() string {\n\toutput := r.changelog.name + \" \" + r.version\n\tif r.isLatest {\n\t\toutput += \" (latest)\"\n\t}\n\treturn output\n}\n\nfunc (r *release) Link() string {\n\treturn \"[\" + r.Title() + \"](\" + r.url + \")\"\n}\n\nfunc (r *release) Render() string {\n\toutput := \"\"\n\toutput += \"## \" + r.Link() + \"\\n\\n\"\n\tif r.notes != \"\" {\n\t\toutput += r.notes + \"\\n\\n\"\n\t}\n\treturn output\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "stack", + "path": "gno.land/p/demo/stack", + "files": [ + { + "name": "stack.gno", + "body": "package stack\n\ntype Stack struct {\n\ttop *node\n\tlength int\n}\n\ntype node struct {\n\tvalue interface{}\n\tprev *node\n}\n\nfunc New() *Stack {\n\treturn \u0026Stack{nil, 0}\n}\n\nfunc (s *Stack) Len() int {\n\treturn s.length\n}\n\nfunc (s *Stack) Top() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\treturn s.top.value\n}\n\nfunc (s *Stack) Pop() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\n\tnode := s.top\n\ts.top = node.prev\n\ts.length -= 1\n\treturn node.value\n}\n\nfunc (s *Stack) Push(value interface{}) {\n\tnode := \u0026node{value, s.top}\n\ts.top = node\n\ts.length += 1\n}\n" + }, + { + "name": "stack_test.gno", + "body": "package stack\n\nimport \"testing\"\n\nfunc TestStack(t *testing.T) {\n\ts := New() // Empty stack\n\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\n\t}\n\n\ts.Push(1)\n\n\tif s.Len() != 1 {\n\t\tt.Errorf(\"s.Len(): expected 1; got %d\", s.Len())\n\t}\n\n\tif top := s.Top(); top.(int) != 1 {\n\t\tt.Errorf(\"s.Top(): expected 1; got %v\", top.(int))\n\t}\n\n\tif elem := s.Pop(); elem.(int) != 1 {\n\t\tt.Errorf(\"s.Pop(): expected 1; got %v\", elem.(int))\n\t}\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "svg", + "path": "gno.land/p/demo/svg", + "files": [ + { + "name": "doc.gno", + "body": "/*\nPackage svg is a minimalist SVG generation library for Gno.\n\nThe svg package provides a simple and lightweight solution for programmatically generating SVG (Scalable Vector Graphics) markup in Gno. It allows you to create basic shapes like rectangles and circles, and output the generated SVG to a\n\nExample:\n\n\timport \"gno.land/p/demo/svg\"\"\n\n\tfunc Foo() string {\n\t canvas := svg.Canvas{Width: 200, Height: 200}\n\t canvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\t canvas.DrawCircle(100, 100, 50, \"blue\")\n\t return canvas.String()\n\t}\n*/\npackage svg // import \"gno.land/p/demo/svg\"\n" + }, + { + "name": "svg.gno", + "body": "package svg\n\nimport \"gno.land/p/demo/ufmt\"\n\ntype Canvas struct {\n\tWidth int\n\tHeight int\n\tElems []Elem\n}\n\ntype Elem interface{ String() string }\n\nfunc (c Canvas) String() string {\n\toutput := \"\"\n\toutput += ufmt.Sprintf(`\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\"\u003e`, c.Width, c.Height)\n\tfor _, elem := range c.Elems {\n\t\toutput += elem.String()\n\t}\n\toutput += \"\u003c/svg\u003e\"\n\treturn output\n}\n\nfunc (c *Canvas) Append(elem Elem) {\n\tc.Elems = append(c.Elems, elem)\n}\n\ntype Circle struct {\n\tCX int // center X\n\tCY int // center Y\n\tR int // radius\n\tFill string\n}\n\nfunc (c Circle) String() string {\n\treturn ufmt.Sprintf(`\u003ccircle cx=\"%d\" cy=\"%d\" r=\"%d\" fill=\"%s\" /\u003e`, c.CX, c.CY, c.R, c.Fill)\n}\n\nfunc (c *Canvas) DrawCircle(cx, cy, r int, fill string) {\n\tc.Append(Circle{\n\t\tCX: cx,\n\t\tCY: cy,\n\t\tR: r,\n\t\tFill: fill,\n\t})\n}\n\ntype Rectangle struct {\n\tX, Y, Width, Height int\n\tFill string\n}\n\nfunc (c Rectangle) String() string {\n\treturn ufmt.Sprintf(`\u003crect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" /\u003e`, c.X, c.Y, c.Width, c.Height, c.Fill)\n}\n\nfunc (c *Canvas) DrawRectangle(x, y, width, height int, fill string) {\n\tc.Append(Rectangle{\n\t\tX: x,\n\t\tY: y,\n\t\tWidth: width,\n\t\tHeight: height,\n\t\tFill: fill,\n\t})\n}\n\ntype Text struct {\n\tX, Y int\n\tText, Fill string\n}\n\nfunc (c Text) String() string {\n\treturn ufmt.Sprintf(`\u003ctext x=\"%d\" y=\"%d\" fill=\"%s\"\u003e%s\u003c/text\u003e`, c.X, c.Y, c.Fill, c.Text)\n}\n\nfunc (c *Canvas) DrawText(x, y int, text, fill string) {\n\tc.Append(Text{\n\t\tX: x,\n\t\tY: y,\n\t\tText: text,\n\t\tFill: fill,\n\t})\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{Width: 500, Height: 500}\n\tcanvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\tcanvas.DrawCircle(100, 100, 50, \"blue\")\n\tcanvas.DrawText(100, 100, \"hello world!\", \"magenta\")\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"100\" cy=\"100\" r=\"50\" fill=\"blue\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\n" + }, + { + "name": "z1_filetest.gno", + "body": "// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{\n\t\tWidth: 500, Height: 500,\n\t\tElems: []svg.Elem{\n\t\t\tsvg.Rectangle{50, 50, 100, 100, \"red\"},\n\t\t\tsvg.Circle{50, 50, 100, \"red\"},\n\t\t\tsvg.Text{100, 100, \"hello world!\", \"magenta\"},\n\t\t},\n\t}\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"50\" cy=\"50\" r=\"100\" fill=\"red\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "tamagotchi", + "path": "gno.land/p/demo/tamagotchi", + "files": [ + { + "name": "tamagotchi.gno", + "body": "package tamagotchi\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Tamagotchi structure\ntype Tamagotchi struct {\n\tname string\n\thunger int\n\thappiness int\n\thealth int\n\tage int\n\tmaxAge int\n\tsleepy int\n\tcreated time.Time\n\tlastUpdated time.Time\n}\n\nfunc New(name string) *Tamagotchi {\n\tnow := time.Now()\n\treturn \u0026Tamagotchi{\n\t\tname: name,\n\t\thunger: 50,\n\t\thappiness: 50,\n\t\thealth: 50,\n\t\tmaxAge: 100,\n\t\tlastUpdated: now,\n\t\tcreated: now,\n\t}\n}\n\nfunc (t *Tamagotchi) Name() string {\n\tt.update()\n\treturn t.name\n}\n\nfunc (t *Tamagotchi) Hunger() int {\n\tt.update()\n\treturn t.hunger\n}\n\nfunc (t *Tamagotchi) Happiness() int {\n\tt.update()\n\treturn t.happiness\n}\n\nfunc (t *Tamagotchi) Health() int {\n\tt.update()\n\treturn t.health\n}\n\nfunc (t *Tamagotchi) Age() int {\n\tt.update()\n\treturn t.age\n}\n\nfunc (t *Tamagotchi) Sleepy() int {\n\tt.update()\n\treturn t.sleepy\n}\n\n// Feed method for Tamagotchi\nfunc (t *Tamagotchi) Feed() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.hunger = bound(t.hunger-10, 0, 100)\n}\n\n// Play method for Tamagotchi\nfunc (t *Tamagotchi) Play() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.happiness = bound(t.happiness+10, 0, 100)\n}\n\n// Heal method for Tamagotchi\nfunc (t *Tamagotchi) Heal() {\n\tt.update()\n\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.health = bound(t.health+10, 0, 100)\n}\n\nfunc (t Tamagotchi) dead() bool { return t.health == 0 }\n\n// Update applies changes based on the duration since the last update\nfunc (t *Tamagotchi) update() {\n\tif t.dead() {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tif t.lastUpdated == now {\n\t\treturn\n\t}\n\n\tduration := now.Sub(t.lastUpdated)\n\telapsedMins := int(duration.Minutes())\n\n\tt.hunger = bound(t.hunger+elapsedMins, 0, 100)\n\tt.happiness = bound(t.happiness-elapsedMins, 0, 100)\n\tt.health = bound(t.health-elapsedMins, 0, 100)\n\tt.sleepy = bound(t.sleepy+elapsedMins, 0, 100)\n\n\t// age is hours since created\n\tt.age = int(now.Sub(t.created).Hours())\n\tif t.age \u003e t.maxAge {\n\t\tt.age = t.maxAge\n\t\tt.health = 0\n\t}\n\tif t.health == 0 {\n\t\tt.sleepy = 0\n\t\tt.happiness = 0\n\t\tt.hunger = 0\n\t}\n\n\tt.lastUpdated = now\n}\n\n// Face returns an ASCII art representation of the Tamagotchi's current state\nfunc (t *Tamagotchi) Face() string {\n\tt.update()\n\treturn t.face()\n}\n\nfunc (t *Tamagotchi) face() string {\n\tswitch {\n\tcase t.health == 0:\n\t\treturn \"😵\" // dead face\n\tcase t.health \u003c 30:\n\t\treturn \"😷\" // sick face\n\tcase t.happiness \u003c 30:\n\t\treturn \"😢\" // sad face\n\tcase t.hunger \u003e 70:\n\t\treturn \"😫\" // hungry face\n\tcase t.sleepy \u003e 70:\n\t\treturn \"😴\" // sleepy face\n\tdefault:\n\t\treturn \"😃\" // happy face\n\t}\n}\n\n// Markdown method for Tamagotchi\nfunc (t *Tamagotchi) Markdown() string {\n\tt.update()\n\treturn ufmt.Sprintf(`# %s %s\n\n* age: %d\n* hunger: %d\n* happiness: %d\n* health: %d\n* sleepy: %d`,\n\t\tt.name, t.Face(),\n\t\tt.age, t.hunger, t.happiness, t.health, t.sleepy,\n\t)\n}\n\nfunc bound(n, min, max int) int {\n\tif n \u003c min {\n\t\treturn min\n\t}\n\tif n \u003e max {\n\t\treturn max\n\t}\n\treturn n\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "package main\n\nimport (\n\t\"time\"\n\n\t\"internal/os_test\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n)\n\nfunc main() {\n\tt := tamagotchi.New(\"Gnome\")\n\n\tprintln(\"\\n-- INITIAL\\n\")\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- FEEDx3, PLAYx2, HEALx4\\n\")\n\tt.Feed()\n\tt.Feed()\n\tt.Feed()\n\tt.Play()\n\tt.Play()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n}\n\n// Output:\n// -- INITIAL\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 70\n// * happiness: 30\n// * health: 30\n// * sleepy: 20\n//\n// -- FEEDx3, PLAYx2, HEALx4\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 40\n// * happiness: 50\n// * health: 70\n// * sleepy: 20\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 60\n// * happiness: 30\n// * health: 50\n// * sleepy: 40\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "subtests", + "path": "gno.land/p/demo/tests/subtests", + "files": [ + { + "name": "subtests.gno", + "body": "package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "subtests", + "path": "gno.land/r/demo/tests/subtests", + "files": [ + { + "name": "subtests.gno", + "body": "package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "tests", + "path": "gno.land/r/demo/tests", + "files": [ + { + "name": "README.md", + "body": "Modules here are only useful for file realm tests.\nThey can be safely ignored for other purposes.\n" + }, + { + "name": "interfaces.gno", + "body": "package tests\n\nimport (\n\t\"strconv\"\n)\n\ntype Stringer interface {\n\tString() string\n}\n\nvar stringers []Stringer\n\nfunc AddStringer(str Stringer) {\n\t// NOTE: this is ridiculous, a slice that will become too long\n\t// eventually. Don't do this in production programs; use\n\t// gno.land/p/demo/avl or similar structures.\n\tstringers = append(stringers, str)\n}\n\nfunc Render(path string) string {\n\tres := \"\"\n\t// NOTE: like the function above, this function too will eventually\n\t// become too expensive to call.\n\tfor i, stringer := range stringers {\n\t\tres += strconv.Itoa(i) + \": \" + stringer.String() + \"\\n\"\n\t}\n\treturn res\n}\n" + }, + { + "name": "realm_compositelit.gno", + "body": "package tests\n\ntype (\n\tWord uint\n\tnat []Word\n)\n\nvar zero = \u0026Int{\n\tneg: true,\n\tabs: []Word{0},\n}\n\n// structLit\ntype Int struct {\n\tneg bool\n\tabs nat\n}\n\nfunc GetZeroType() nat {\n\ta := zero.abs\n\treturn a\n}\n" + }, + { + "name": "realm_method38d.gno", + "body": "package tests\n\nvar abs nat\n\nfunc (n nat) Add() nat {\n\treturn []Word{0}\n}\n\nfunc GetAbs() nat {\n\tabs = []Word{0}\n\n\treturn abs\n}\n\nfunc AbsAdd() nat {\n\trt := GetAbs().Add()\n\n\treturn rt\n}\n" + }, + { + "name": "tests.gno", + "body": "package tests\n\nimport (\n\t\"std\"\n\n\trsubtests \"gno.land/r/demo/tests/subtests\"\n)\n\nvar counter int\n\nfunc IncCounter() {\n\tcounter++\n}\n\nfunc Counter() int {\n\treturn counter\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\nvar initOrigCaller = std.GetOrigCaller()\n\nfunc InitOrigCaller() std.Address {\n\treturn initOrigCaller\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\n}\n\nfunc CallSubtestsAssertOriginCall() {\n\trsubtests.CallAssertOriginCall()\n}\n\nfunc CallSubtestsIsOriginCall() bool {\n\treturn rsubtests.CallIsOriginCall()\n}\n\n//----------------------------------------\n// Test structure to ensure cross-realm modification is prevented.\n\ntype TestRealmObject struct {\n\tField string\n}\n\nfunc ModifyTestRealmObject(t *TestRealmObject) {\n\tt.Field += \"_modified\"\n}\n\nfunc (t *TestRealmObject) Modify() {\n\tt.Field += \"_modified\"\n}\n\n//----------------------------------------\n// Test helpers to test a particular realm bug.\n\ntype TestNode struct {\n\tName string\n\tChild *TestNode\n}\n\nvar (\n\tgTestNode1 *TestNode\n\tgTestNode2 *TestNode\n\tgTestNode3 *TestNode\n)\n\nfunc InitTestNodes() {\n\tgTestNode1 = \u0026TestNode{Name: \"first\"}\n\tgTestNode2 = \u0026TestNode{Name: \"second\", Child: \u0026TestNode{Name: \"second's child\"}}\n}\n\nfunc ModTestNodes() {\n\ttmp := \u0026TestNode{}\n\ttmp.Child = gTestNode2.Child\n\tgTestNode3 = tmp // set to new-real\n\t// gTestNode1 = tmp.Child // set back to original is-real\n\tgTestNode3 = nil // delete.\n}\n\nfunc PrintTestNodes() {\n\tprintln(gTestNode2.Child.Name)\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc GetRSubtestsPrevRealm() std.Realm {\n\treturn rsubtests.GetPrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n" + }, + { + "name": "tests_test.gno", + "body": "package tests\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nfunc TestAssertOriginCall(t *testing.T) {\n\t// CallAssertOriginCall(): no panic\n\tCallAssertOriginCall()\n\tif !CallIsOriginCall() {\n\t\tt.Errorf(\"expected IsOriginCall=true but got false\")\n\t}\n\n\t// CallAssertOriginCall() from a block: panic\n\texpectedReason := \"invalid non-origin call\"\n\tfunc() {\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tif r == nil || r.(string) != expectedReason {\n\t\t\t\tt.Errorf(\"expected panic with '%v', got '%v'\", expectedReason, r)\n\t\t\t}\n\t\t}()\n\t\t// if called inside a function literal, this is no longer an origin call\n\t\t// because there's one additional frame (the function literal block).\n\t\tif CallIsOriginCall() {\n\t\t\tt.Errorf(\"expected IsOriginCall=false but got true\")\n\t\t}\n\t\tCallAssertOriginCall()\n\t}()\n\n\t// CallSubtestsAssertOriginCall(): panic\n\tdefer func() {\n\t\tr := recover()\n\t\tif r == nil || r.(string) != expectedReason {\n\t\t\tt.Errorf(\"expected panic with '%v', got '%v'\", expectedReason, r)\n\t\t}\n\t}()\n\tif CallSubtestsIsOriginCall() {\n\t\tt.Errorf(\"expected IsOriginCall=false but got true\")\n\t}\n\tCallSubtestsAssertOriginCall()\n}\n\nfunc TestPrevRealm(t *testing.T) {\n\tvar (\n\t\tuser1Addr = std.DerivePkgAddr(\"user1.gno\")\n\t\trTestsAddr = std.DerivePkgAddr(\"gno.land/r/demo/tests\")\n\t)\n\t// When a single realm in the frames, PrevRealm returns the user\n\tif addr := GetPrevRealm().Addr(); addr != user1Addr {\n\t\tt.Errorf(\"want GetPrevRealm().Addr==%s, got %s\", user1Addr, addr)\n\t}\n\t// When 2 or more realms in the frames, PrevRealm returns the second to last\n\tif addr := GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr {\n\t\tt.Errorf(\"want GetRSubtestsPrevRealm().Addr==%s, got %s\", rTestsAddr, addr)\n\t}\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "package main\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(\"tests.CallIsOriginCall:\", tests.CallIsOriginCall())\n\ttests.CallAssertOriginCall()\n\tprintln(\"tests.CallAssertOriginCall doesn't panic when called directly\")\n\n\t{\n\t\t// if called inside a block, this is no longer an origin call because\n\t\t// there's one additional frame (the block).\n\t\tprintln(\"tests.CallIsOriginCall:\", tests.CallIsOriginCall())\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tprintln(\"tests.AssertOriginCall panics if when called inside a function literal:\", r)\n\t\t}()\n\t\ttests.CallAssertOriginCall()\n\t}\n}\n\n// Output:\n// tests.CallIsOriginCall: true\n// tests.CallAssertOriginCall doesn't panic when called directly\n// tests.CallIsOriginCall: true\n// tests.AssertOriginCall panics if when called inside a function literal: undefined\n" + }, + { + "name": "z1_filetest.gno", + "body": "package main\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(tests.Counter())\n\ttests.IncCounter()\n\tprintln(tests.Counter())\n}\n\n// Output:\n// 0\n// 1\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "tests", + "path": "gno.land/p/demo/tests", + "files": [ + { + "name": "README.md", + "body": "Modules here are only useful for file realm tests.\nThey can be safely ignored for other purposes.\n" + }, + { + "name": "tests.gno", + "body": "package tests\n\nimport (\n\t\"std\"\n\n\tpsubtests \"gno.land/p/demo/tests/subtests\"\n\t\"gno.land/r/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nconst World = \"world\"\n\n// IncCounter demonstrates that it's possible to call a realm function from\n// a package. So a package can potentially write into the store, by calling\n// an other realm.\nfunc IncCounter() {\n\ttests.IncCounter()\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\n//----------------------------------------\n// cross realm test vars\n\ntype TestRealmObject2 struct {\n\tField string\n}\n\nfunc (o2 *TestRealmObject2) Modify() {\n\to2.Field = \"modified\"\n}\n\nvar (\n\tsomevalue1 TestRealmObject2\n\tSomeValue2 TestRealmObject2\n\tSomeValue3 *TestRealmObject2\n)\n\nfunc init() {\n\tsomevalue1 = TestRealmObject2{Field: \"init\"}\n\tSomeValue2 = TestRealmObject2{Field: \"init\"}\n\tSomeValue3 = \u0026TestRealmObject2{Field: \"init\"}\n}\n\nfunc ModifyTestRealmObject2a() {\n\tsomevalue1.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2b() {\n\tSomeValue2.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2c() {\n\tSomeValue3.Field = \"modified\"\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc GetPSubtestsPrevRealm() std.Realm {\n\treturn psubtests.GetPrevRealm()\n}\n\nfunc GetRTestsGetPrevRealm() std.Realm {\n\treturn rtests.GetPrevRealm()\n}\n\n// Warning: unsafe pattern.\nfunc Exec(fn func()) {\n\tfn()\n}\n" + }, + { + "name": "tests_test.gno", + "body": "package tests_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/tests\"\n)\n\nvar World = \"WORLD\"\n\nfunc TestGetHelloWorld(t *testing.T) {\n\t// tests.World is 'world'\n\ts := \"hello \" + tests.World + World\n\tconst want = \"hello worldWORLD\"\n\tif s != want {\n\t\tt.Errorf(\"got %q want %q\", s, want)\n\t}\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "package main\n\nimport (\n\tptests \"gno.land/p/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(rtests.Counter())\n\tptests.IncCounter()\n\tprintln(rtests.Counter())\n}\n\n// Output:\n// 0\n// 1\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "p_crossrealm", + "path": "gno.land/p/demo/tests/p_crossrealm", + "files": [ + { + "name": "p_crossrealm.gno", + "body": "package p_crossrealm\n\ntype Stringer interface {\n\tString() string\n}\n\ntype Container struct {\n\tA int\n\tB Stringer\n}\n\nfunc (c *Container) Touch() *Container {\n\tc.A += 1\n\treturn c\n}\n\nfunc (c *Container) Print() {\n\tprintln(\"A:\", c.A)\n\tif c.B == nil {\n\t\tprintln(\"B: undefined\")\n\t} else {\n\t\tprintln(\"B:\", c.B.String())\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "todolist", + "path": "gno.land/p/demo/todolist", + "files": [ + { + "name": "todolist.gno", + "body": "package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.GetOrigCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n" + }, + { + "name": "todolist_test.gno", + "body": "package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tif todoList.GetTodolistTitle() != title {\n\t\tt.Errorf(\"Expected title %q, got %q\", title, todoList.GetTodolistTitle())\n\t}\n\n\tif len(todoList.GetTasks()) != 0 {\n\t\tt.Errorf(\"Expected 0 tasks, got %d\", len(todoList.GetTasks()))\n\t}\n\n\tif todoList.GetTodolistOwner() != std.GetOrigCaller() {\n\t\tt.Errorf(\"Expected owner %v, got %v\", std.GetOrigCaller(), todoList.GetTodolistOwner())\n\t}\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tif task.Title != title {\n\t\tt.Errorf(\"Expected title %q, got %q\", title, task.Title)\n\t}\n\n\tif task.Done {\n\t\tt.Errorf(\"Expected task to be not done, but it is done\")\n\t}\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\tif len(tasks) != 1 {\n\t\tt.Errorf(\"Expected 1 task, got %d\", len(tasks))\n\t}\n\n\tif tasks[0] != task {\n\t\tt.Errorf(\"Expected task %v, got %v\", task, tasks[0])\n\t}\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\n\tif !task.Done {\n\t\tt.Errorf(\"Expected task to be done, but it is not done\")\n\t}\n\n\tToggleTaskStatus(task)\n\n\tif task.Done {\n\t\tt.Errorf(\"Expected task to be not done, but it is done\")\n\t}\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tif len(tasks) != 0 {\n\t\tt.Errorf(\"Expected 0 tasks, got %d\", len(tasks))\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ui", + "path": "gno.land/p/demo/ui", + "files": [ + { + "name": "ui.gno", + "body": "package ui\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype DOM struct {\n\t// metadata\n\tPrefix string\n\tTitle string\n\tWithComments bool\n\tClasses []string\n\n\t// elements\n\tHeader Element\n\tBody Element\n\tFooter Element\n}\n\nfunc (dom DOM) String() string {\n\tclasses := strings.Join(dom.Classes, \" \")\n\n\toutput := \"\"\n\n\tif classes != \"\" {\n\t\toutput += \"\u003cmain class='\" + classes + \"'\u003e\" + \"\\n\\n\"\n\t}\n\n\tif dom.Title != \"\" {\n\t\toutput += H1(dom.Title).String(dom) + \"\\n\"\n\t}\n\n\tif header := dom.Header.String(dom); header != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- header --\u003e\"\n\t\t}\n\t\toutput += header + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /header --\u003e\"\n\t\t}\n\t}\n\n\tif body := dom.Body.String(dom); body != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- body --\u003e\"\n\t\t}\n\t\toutput += body + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /body --\u003e\"\n\t\t}\n\t}\n\n\tif footer := dom.Footer.String(dom); footer != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- footer --\u003e\"\n\t\t}\n\t\toutput += footer + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /footer --\u003e\"\n\t\t}\n\t}\n\n\tif classes != \"\" {\n\t\toutput += \"\u003c/main\u003e\"\n\t}\n\n\t// TODO: cleanup double new-lines.\n\n\treturn output\n}\n\ntype Jumbotron []DomStringer\n\nfunc (j Jumbotron) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tfor _, elem := range j {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\toutput += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\treturn output\n}\n\n// XXX: rename Element to Div?\ntype Element []DomStringer\n\nfunc (e *Element) Append(elems ...DomStringer) {\n\t*e = append(*e, elems...)\n}\n\nfunc (e *Element) String(dom DOM) string {\n\toutput := \"\"\n\tfor _, elem := range *e {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\treturn output\n}\n\ntype Breadcrumb []DomStringer\n\nfunc (b *Breadcrumb) Append(elems ...DomStringer) {\n\t*b = append(*b, elems...)\n}\n\nfunc (b Breadcrumb) String(dom DOM) string {\n\toutput := \"\"\n\tfor idx, entry := range b {\n\t\tif idx \u003e 0 {\n\t\t\toutput += \" / \"\n\t\t}\n\t\toutput += entry.String(dom)\n\t}\n\treturn output\n}\n\ntype Columns struct {\n\tMaxWidth int\n\tColumns []Element\n}\n\nfunc (c *Columns) Append(elems ...Element) {\n\tc.Columns = append(c.Columns, elems...)\n}\n\nfunc (c Columns) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"columns-` + strconv.Itoa(c.MaxWidth) + `\"\u003e` + \"\\n\"\n\tfor _, entry := range c.Columns {\n\t\toutput += `\u003cdiv class=\"column\"\u003e` + \"\\n\\n\"\n\t\toutput += entry.String(dom)\n\t\toutput += \"\u003c/div\u003e\u003c!-- /column--\u003e\\n\"\n\t}\n\toutput += \"\u003c/div\u003e\u003c!-- /columns-\" + strconv.Itoa(c.MaxWidth) + \" --\u003e\\n\"\n\treturn output\n}\n\ntype Link struct {\n\tText string\n\tPath string\n\tURL string\n}\n\n// TODO: image\n\n// TODO: pager\n\nfunc (l Link) String(dom DOM) string {\n\turl := \"\"\n\tswitch {\n\tcase l.Path != \"\" \u0026\u0026 l.URL != \"\":\n\t\tpanic(\"a link should have a path or a URL, not both.\")\n\tcase l.Path != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.Path\n\t\t}\n\t\turl = dom.Prefix + l.Path\n\tcase l.URL != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.URL\n\t\t}\n\t\turl = l.URL\n\t}\n\n\treturn \"[\" + l.Text + \"](\" + url + \")\"\n}\n\ntype BulletList []DomStringer\n\nfunc (bl BulletList) String(dom DOM) string {\n\toutput := \"\"\n\n\tfor _, entry := range bl {\n\t\toutput += \"- \" + entry.String(dom) + \"\\n\"\n\t}\n\n\treturn output\n}\n\nfunc Text(s string) DomStringer {\n\treturn Raw{Content: s}\n}\n\ntype DomStringer interface {\n\tString(dom DOM) string\n}\n\ntype Raw struct {\n\tContent string\n}\n\nfunc (r Raw) String(_ DOM) string {\n\treturn r.Content\n}\n\ntype (\n\tH1 string\n\tH2 string\n\tH3 string\n\tH4 string\n\tH5 string\n\tH6 string\n\tBold string\n\tItalic string\n\tCode string\n\tParagraph string\n\tQuote string\n\tHR struct{}\n)\n\nfunc (text H1) String(_ DOM) string { return \"# \" + string(text) + \"\\n\" }\nfunc (text H2) String(_ DOM) string { return \"## \" + string(text) + \"\\n\" }\nfunc (text H3) String(_ DOM) string { return \"### \" + string(text) + \"\\n\" }\nfunc (text H4) String(_ DOM) string { return \"#### \" + string(text) + \"\\n\" }\nfunc (text H5) String(_ DOM) string { return \"##### \" + string(text) + \"\\n\" }\nfunc (text H6) String(_ DOM) string { return \"###### \" + string(text) + \"\\n\" }\nfunc (text Quote) String(_ DOM) string { return \"\u003e \" + string(text) + \"\\n\" }\nfunc (text Bold) String(_ DOM) string { return \"**\" + string(text) + \"**\" }\nfunc (text Italic) String(_ DOM) string { return \"_\" + string(text) + \"_\" }\nfunc (text Paragraph) String(_ DOM) string { return \"\\n\" + string(text) + \"\\n\" }\nfunc (_ HR) String(_ DOM) string { return \"\\n---\\n\" }\n\nfunc (text Code) String(_ DOM) string {\n\t// multiline\n\tif strings.Contains(string(text), \"\\n\") {\n\t\treturn \"\\n```\\n\" + string(text) + \"\\n```\\n\"\n\t}\n\n\t// single line\n\treturn \"`\" + string(text) + \"`\"\n}\n" + }, + { + "name": "ui_test.gno", + "body": "package ui\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "watchdog", + "path": "gno.land/p/demo/watchdog", + "files": [ + { + "name": "watchdog.gno", + "body": "package watchdog\n\nimport \"time\"\n\ntype Watchdog struct {\n\tDuration time.Duration\n\tlastUpdate time.Time\n\tlastDown time.Time\n}\n\nfunc (w *Watchdog) Alive() {\n\tnow := time.Now()\n\tif !w.IsAlive() {\n\t\tw.lastDown = now\n\t}\n\tw.lastUpdate = now\n}\n\nfunc (w Watchdog) Status() string {\n\tif w.IsAlive() {\n\t\treturn \"OK\"\n\t}\n\treturn \"KO\"\n}\n\nfunc (w Watchdog) IsAlive() bool {\n\treturn time.Since(w.lastUpdate) \u003c w.Duration\n}\n\nfunc (w Watchdog) UpSince() time.Time {\n\treturn w.lastDown\n}\n\nfunc (w Watchdog) DownSince() time.Time {\n\tif !w.IsAlive() {\n\t\treturn w.lastUpdate\n\t}\n\treturn time.Time{}\n}\n" + }, + { + "name": "watchdog_test.gno", + "body": "package watchdog\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tw := Watchdog{Duration: 5 * time.Minute}\n\tuassert.False(t, w.IsAlive())\n\tw.Alive()\n\tuassert.True(t, w.IsAlive())\n\t// XXX: add more tests when we'll be able to \"skip time\".\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "proposal", + "path": "gno.land/p/gov/proposal", + "files": [ + { + "name": "proposal.gno", + "body": "// Package proposal provides a structure for executing proposals.\npackage proposal\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/context\"\n)\n\nvar errNotGovDAO = errors.New(\"only r/gov/dao can be the caller\")\n\n// NewExecutor creates a new executor with the provided callback function.\nfunc NewExecutor(callback func() error) Executor {\n\treturn \u0026executorImpl{\n\t\tcallback: callback,\n\t\tdone: false,\n\t}\n}\n\n// NewCtxExecutor creates a new executor with the provided callback function.\nfunc NewCtxExecutor(callback func(ctx context.Context) error) Executor {\n\treturn \u0026executorImpl{\n\t\tcallbackCtx: callback,\n\t\tdone: false,\n\t}\n}\n\n// executorImpl is an implementation of the Executor interface.\ntype executorImpl struct {\n\tcallback func() error\n\tcallbackCtx func(ctx context.Context) error\n\tdone bool\n\tsuccess bool\n}\n\n// Execute runs the executor's callback function.\nfunc (exec *executorImpl) Execute() error {\n\tif exec.done {\n\t\treturn ErrAlreadyDone\n\t}\n\n\t// Verify the executor is r/gov/dao\n\tassertCalledByGovdao()\n\n\tvar err error\n\tif exec.callback != nil {\n\t\terr = exec.callback()\n\t} else if exec.callbackCtx != nil {\n\t\tctx := context.WithValue(context.Empty(), statusContextKey, approvedStatus)\n\t\terr = exec.callbackCtx(ctx)\n\t}\n\texec.done = true\n\texec.success = err == nil\n\n\treturn err\n}\n\n// IsDone returns whether the executor has been executed.\nfunc (exec *executorImpl) IsDone() bool {\n\treturn exec.done\n}\n\n// IsSuccessful returns whether the execution was successful.\nfunc (exec *executorImpl) IsSuccessful() bool {\n\treturn exec.success\n}\n\n// IsExpired returns whether the execution had expired or not.\n// This implementation never expires.\nfunc (exec *executorImpl) IsExpired() bool {\n\treturn false\n}\n\nfunc IsApprovedByGovdaoContext(ctx context.Context) bool {\n\tv := ctx.Value(statusContextKey)\n\tif v == nil {\n\t\treturn false\n\t}\n\tvs, ok := v.(string)\n\treturn ok \u0026\u0026 vs == approvedStatus\n}\n\nfunc AssertContextApprovedByGovDAO(ctx context.Context) {\n\tif !IsApprovedByGovdaoContext(ctx) {\n\t\tpanic(\"not approved by govdao\")\n\t}\n}\n\n// assertCalledByGovdao asserts that the calling Realm is /r/gov/dao\nfunc assertCalledByGovdao() {\n\tcaller := std.CurrentRealm().PkgPath()\n\n\tif caller != daoPkgPath {\n\t\tpanic(errNotGovDAO)\n\t}\n}\n\ntype propContextKey string\n\nfunc (k propContextKey) String() string { return string(k) }\n\nconst (\n\tstatusContextKey = propContextKey(\"govdao-prop-status\")\n\tapprovedStatus = \"approved\"\n)\n" + }, + { + "name": "proposal_test.gno", + "body": "package proposal\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestExecutor(t *testing.T) {\n\tt.Parallel()\n\n\tverifyProposalFailed := func(e Executor) {\n\t\tuassert.True(t, e.IsDone(), \"expected proposal to be done\")\n\t\tuassert.False(t, e.IsSuccessful(), \"expected proposal to fail\")\n\t}\n\n\tverifyProposalSucceeded := func(e Executor) {\n\t\tuassert.True(t, e.IsDone(), \"expected proposal to be done\")\n\t\tuassert.True(t, e.IsSuccessful(), \"expected proposal to be successful\")\n\t}\n\n\tt.Run(\"govdao not caller\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\te := NewExecutor(cb)\n\n\t\turequire.False(t, e.IsDone(), \"expected status to be NotExecuted\")\n\n\t\t// Execute as not the /r/gov/dao caller\n\t\tuassert.PanicsWithMessage(t, errNotGovDAO.Error(), func() {\n\t\t\t_ = e.Execute()\n\t\t})\n\n\t\tuassert.False(t, called, \"expected proposal to not execute\")\n\t})\n\n\tt.Run(\"execution successful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\te := NewExecutor(cb)\n\n\t\turequire.False(t, e.IsDone(), \"expected status to be NotExecuted\")\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\terr := e.Execute()\n\n\t\t\tuassert.NoError(t, err)\n\t\t})\n\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\n\t\t// Make sure the execution params are correct\n\t\tverifyProposalSucceeded(e)\n\t})\n\n\tt.Run(\"execution unsuccessful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\texpectedErr = errors.New(\"unexpected\")\n\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn expectedErr\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\te := NewExecutor(cb)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\terr := e.Execute()\n\n\t\t\tuassert.ErrorIs(t, err, expectedErr)\n\t\t})\n\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\n\t\t// Make sure the execution params are correct\n\t\tverifyProposalFailed(e)\n\t})\n\n\tt.Run(\"proposal already executed\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\te := NewExecutor(cb)\n\n\t\turequire.False(t, e.IsDone(), \"expected status to be NotExecuted\")\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tuassert.NoError(t, e.Execute())\n\t\t})\n\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\n\t\t// Make sure the execution params are correct\n\t\tverifyProposalSucceeded(e)\n\n\t\t// Attempt to execute the proposal again\n\t\tuassert.NotPanics(t, func() {\n\t\t\terr := e.Execute()\n\n\t\t\tuassert.ErrorIs(t, err, ErrAlreadyDone)\n\t\t})\n\t})\n}\n" + }, + { + "name": "types.gno", + "body": "// Package proposal defines types for proposal execution.\npackage proposal\n\nimport \"errors\"\n\n// Executor represents a minimal closure-oriented proposal design.\n// It is intended to be used by a govdao governance proposal (v1, v2, etc).\ntype Executor interface {\n\t// Execute executes the given proposal, and returns any error encountered\n\t// during the execution\n\tExecute() error\n\n\t// IsDone returns a flag indicating if the proposal was executed\n\tIsDone() bool\n\n\t// IsSuccessful returns a flag indicating if the proposal was executed\n\t// and is successful\n\tIsSuccessful() bool // IsDone() \u0026\u0026 !err\n\n\t// IsExpired returns whether the execution had expired or not.\n\tIsExpired() bool\n}\n\n// ErrAlreadyDone is the error returned when trying to execute an already\n// executed proposal.\nvar ErrAlreadyDone = errors.New(\"already executed\")\n\n// Status enum.\ntype Status string\n\nconst (\n\tNotExecuted Status = \"not_executed\"\n\tSucceeded Status = \"succeeded\"\n\tFailed Status = \"failed\"\n)\n\nconst daoPkgPath = \"gno.land/r/gov/dao\" // TODO: make sure this is configurable through r/sys/vars\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "validators", + "path": "gno.land/p/sys/validators", + "files": [ + { + "name": "types.gno", + "body": "package validators\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\n// ValsetProtocol defines the validator set protocol (PoA / PoS / PoC / ?)\ntype ValsetProtocol interface {\n\t// AddValidator adds a new validator to the validator set.\n\t// If the validator is already present, the method should error out\n\t//\n\t// TODO: This API is not ideal -- the address should be derived from\n\t// the public key, and not be passed in as such, but currently Gno\n\t// does not support crypto address derivation\n\tAddValidator(address std.Address, pubKey string, power uint64) (Validator, error)\n\n\t// RemoveValidator removes the given validator from the set.\n\t// If the validator is not present in the set, the method should error out\n\tRemoveValidator(address std.Address) (Validator, error)\n\n\t// IsValidator returns a flag indicating if the given\n\t// bech32 address is part of the validator set\n\tIsValidator(address std.Address) bool\n\n\t// GetValidator returns the validator using the given address\n\tGetValidator(address std.Address) (Validator, error)\n\n\t// GetValidators returns the currently active validator set\n\tGetValidators() []Validator\n}\n\n// Validator represents a single chain validator\ntype Validator struct {\n\tAddress std.Address // bech32 address\n\tPubKey string // bech32 representation of the public key\n\tVotingPower uint64\n}\n\nconst (\n\tValidatorAddedEvent = \"ValidatorAdded\" // emitted when a validator was added to the set\n\tValidatorRemovedEvent = \"ValidatorRemoved\" // emitted when a validator was removed from the set\n)\n\nvar (\n\t// ErrValidatorExists is returned when the validator is already in the set\n\tErrValidatorExists = errors.New(\"validator already exists\")\n\n\t// ErrValidatorMissing is returned when the validator is not in the set\n\tErrValidatorMissing = errors.New(\"validator doesn't exist\")\n)\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "poa", + "path": "gno.land/p/nt/poa", + "files": [ + { + "name": "option.gno", + "body": "package poa\n\nimport \"gno.land/p/sys/validators\"\n\ntype Option func(*PoA)\n\n// WithInitialSet sets the initial PoA validator set\nfunc WithInitialSet(validators []validators.Validator) Option {\n\treturn func(p *PoA) {\n\t\tfor _, validator := range validators {\n\t\t\tp.validators.Set(validator.Address.String(), validator)\n\t\t}\n\t}\n}\n" + }, + { + "name": "poa.gno", + "body": "package poa\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar ErrInvalidVotingPower = errors.New(\"invalid voting power\")\n\n// PoA specifies the Proof of Authority validator set, with simple add / remove constraints.\n//\n// To add:\n// - proposed validator must not be part of the set already\n// - proposed validator voting power must be \u003e 0\n//\n// To remove:\n// - proposed validator must be part of the set already\ntype PoA struct {\n\tvalidators *avl.Tree // std.Address -\u003e validators.Validator\n}\n\n// NewPoA creates a new empty Proof of Authority validator set\nfunc NewPoA(opts ...Option) *PoA {\n\t// Create the empty set\n\tp := \u0026PoA{\n\t\tvalidators: avl.NewTree(),\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\treturn p\n}\n\nfunc (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) {\n\t// Validate that the operation is a valid call.\n\t// Check if the validator is already in the set\n\tif p.IsValidator(address) {\n\t\treturn validators.Validator{}, validators.ErrValidatorExists\n\t}\n\n\t// Make sure the voting power \u003e 0\n\tif power == 0 {\n\t\treturn validators.Validator{}, ErrInvalidVotingPower\n\t}\n\n\tv := validators.Validator{\n\t\tAddress: address,\n\t\tPubKey: pubKey, // TODO: in the future, verify the public key\n\t\tVotingPower: power,\n\t}\n\n\t// Add the validator to the set\n\tp.validators.Set(address.String(), v)\n\n\treturn v, nil\n}\n\nfunc (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) {\n\t// Validate that the operation is a valid call\n\t// Fetch the validator\n\tvalidator, err := p.GetValidator(address)\n\tif err != nil {\n\t\treturn validators.Validator{}, err\n\t}\n\n\t// Remove the validator from the set\n\tp.validators.Remove(address.String())\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) IsValidator(address std.Address) bool {\n\t_, exists := p.validators.Get(address.String())\n\n\treturn exists\n}\n\nfunc (p *PoA) GetValidator(address std.Address) (validators.Validator, error) {\n\tvalidatorRaw, exists := p.validators.Get(address.String())\n\tif !exists {\n\t\treturn validators.Validator{}, validators.ErrValidatorMissing\n\t}\n\n\tvalidator := validatorRaw.(validators.Validator)\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) GetValidators() []validators.Validator {\n\tvals := make([]validators.Validator, 0, p.validators.Size())\n\n\tp.validators.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvalidator := value.(validators.Validator)\n\t\tvals = append(vals, validator)\n\n\t\treturn false\n\t})\n\n\treturn vals\n}\n" + }, + { + "name": "poa_test.gno", + "body": "package poa\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n\t\"gno.land/p/sys/validators\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 1,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestPoA_AddValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator already in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\t\tinitialSet[0].PubKey = proposalKey\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorExists)\n\t})\n\n\tt.Run(\"invalid voting power\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\t\t)\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 0)\n\t\tuassert.ErrorIs(t, err, ErrInvalidVotingPower)\n\t})\n}\n\nfunc TestPoA_AddValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tproposalKey = \"public-key\"\n\t)\n\n\t// Create the protocol with no initial set\n\tp := NewPoA()\n\n\t// Attempt to add the validator\n\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\tuassert.NoError(t, err)\n\n\t// Make sure the validator is added\n\tif !p.IsValidator(proposalAddress) || p.validators.Size() != 1 {\n\t\tt.Fatal(\"address is not validator\")\n\t}\n}\n\nfunc TestPoA_RemoveValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"proposed removal not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to remove the validator\n\t\t_, err := p.RemoveValidator(testutils.TestAddress(\"totally random\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n}\n\nfunc TestPoA_RemoveValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tinitialSet = generateTestValidators(1)\n\t)\n\n\tinitialSet[0].Address = proposalAddress\n\n\t// Create the protocol with an initial set\n\tp := NewPoA(WithInitialSet(initialSet))\n\n\t// Attempt to remove the validator\n\t_, err := p.RemoveValidator(proposalAddress)\n\turequire.NoError(t, err)\n\n\t// Make sure the validator is removed\n\tif p.IsValidator(proposalAddress) || p.validators.Size() != 0 {\n\t\tt.Fatal(\"address is validator\")\n\t}\n}\n\nfunc TestPoA_GetValidator(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\t_, err := p.GetValidator(testutils.TestAddress(\"caller\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n\n\tt.Run(\"validator fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\taddress = testutils.TestAddress(\"caller\")\n\t\t\tpubKey = \"public-key\"\n\t\t\tvotingPower = uint64(10)\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = address\n\t\tinitialSet[0].PubKey = pubKey\n\t\tinitialSet[0].VotingPower = votingPower\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator\n\t\tval, err := p.GetValidator(address)\n\t\turequire.NoError(t, err)\n\n\t\t// Validate the address\n\t\tif val.Address != address {\n\t\t\tt.Fatal(\"invalid address\")\n\t\t}\n\n\t\t// Validate the voting power\n\t\tif val.VotingPower != votingPower {\n\t\t\tt.Fatal(\"invalid voting power\")\n\t\t}\n\n\t\t// Validate the public key\n\t\tif val.PubKey != pubKey {\n\t\t\tt.Fatal(\"invalid public key\")\n\t\t}\n\t})\n}\n\nfunc TestPoA_GetValidators(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != 0 {\n\t\t\tt.Fatal(\"validator set is not empty\")\n\t\t}\n\t})\n\n\tt.Run(\"validator set fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tinitialSet := generateTestValidators(10)\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator set\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != len(initialSet) {\n\t\t\tt.Fatal(\"returned validator set mismatch\")\n\t\t}\n\n\t\tfor _, val := range vals {\n\t\t\tfor _, initialVal := range initialSet {\n\t\t\t\tif val.Address != initialVal.Address {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Validate the voting power\n\t\t\t\tuassert.Equal(t, val.VotingPower, initialVal.VotingPower)\n\n\t\t\t\t// Validate the public key\n\t\t\t\tuassert.Equal(t, val.PubKey, initialVal.PubKey)\n\t\t\t}\n\t\t}\n\t})\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "gnoface", + "path": "gno.land/r/demo/art/gnoface", + "files": [ + { + "name": "gnoface.gno", + "body": "package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(std.GetHeight())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n" + }, + { + "name": "gnoface_test.gno", + "body": "package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tif got != tc.expected {\n\t\t\t\tt.Errorf(\"got %s, expected %s\", got, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tif got != tc.expected {\n\t\t\t\tt.Errorf(\"got %s, expected %s\", got, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "millipede", + "path": "gno.land/r/demo/art/millipede", + "files": [ + { + "name": "millipede.gno", + "body": "package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millpede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n" + }, + { + "name": "millipede_test.gno", + "body": "package millipede\n\nimport \"testing\"\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millpede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millpede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tif got != tc.expected {\n\t\t\t\tt.Errorf(\"expected %s, got %s.\", tc.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "banktest", + "path": "gno.land/r/demo/banktest", + "files": [ + { + "name": "README.md", + "body": "This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) 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 \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"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. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package 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 std.AssertOriginCall()\n caller := std.GetOrigCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.GetOrigSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\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 // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return 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:gnolang/4).\n" + }, + { + "name": "banktest.gno", + "body": "package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.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\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\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\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\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\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\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\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\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" + }, + { + "name": "z_0_filetest.gno", + "body": "// SEND: 100000000ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"main\")\n\tstd.TestSetOrigCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OrigSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n" + }, + { + "name": "z_1_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n" + }, + { + "name": "z_2_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"main\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OrigSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n" + }, + { + "name": "z_3_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"main\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "bar20", + "path": "gno.land/r/demo/bar20", + "files": [ + { + "name": "bar20.gno", + "body": "// Package bar20 is similar to gno.land/r/demo/foo20 but exposes a safe-object\n// that can be used by `maketx run`, another contract importing foo20, and in\n// the future when we'll support `maketx call Token.XXX`.\npackage bar20\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PrevRealm().Addr()\n\tif err := banker.Mint(caller, 1_000_000); err != nil {\n\t\treturn \"error: \" + err.Error()\n\t}\n\treturn \"OK\"\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn banker.RenderHome() // XXX: should be Token.RenderHome()\n\tcase c == 2 \u0026\u0026 parts[0] == \"balance\":\n\t\towner := std.Address(parts[1])\n\t\tbalance := Token.BalanceOf(owner)\n\t\treturn ufmt.Sprintf(\"%d\\n\", balance)\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n" + }, + { + "name": "bar20_test.gno", + "body": "package bar20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestPackage(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(1_000_000))\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "deep", + "path": "gno.land/r/demo/deep/very/deep", + "files": [ + { + "name": "render.gno", + "body": "package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "echo", + "path": "gno.land/r/demo/echo", + "files": [ + { + "name": "echo.gno", + "body": "package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n" + }, + { + "name": "echo_test.gno", + "body": "package echo\n\nimport \"testing\"\n\nfunc Test(t *testing.T) {\n\tif Render(\"aa\") != \"aa\" {\n\t\tt.Fail()\n\t}\n\tif Render(\"\") != \"\" {\n\t\tt.Fail()\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "event", + "path": "gno.land/r/demo/event", + "files": [ + { + "name": "event.gno", + "body": "package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "foo1155", + "path": "gno.land/r/demo/foo1155", + "files": [ + { + "name": "foo1155.gno", + "body": "package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n" + }, + { + "name": "foo1155_test.gno", + "body": "package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "foo20", + "path": "gno.land/r/demo/foo20", + "files": [ + { + "name": "foo20.gno", + "body": "// foo20 is a GRC20 token contract where all the GRC20 methods are proxified\n// with top-level functions. see also gno.land/r/demo/bar20.\npackage foo20\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\n}\n\nfunc TotalSupply() uint64 { return token.TotalSupply() }\n\nfunc BalanceOf(owner pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\treturn token.BalanceOf(ownerAddr)\n}\n\nfunc Allowance(owner, spender pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\tspenderAddr := users.Resolve(spender)\n\treturn token.Allowance(ownerAddr, spenderAddr)\n}\n\nfunc Transfer(to pusers.AddressOrName, amount uint64) {\n\ttoAddr := users.Resolve(to)\n\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.Approve(spenderAddr, amount))\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, amount uint64) {\n\tfromAddr := users.Resolve(from)\n\ttoAddr := users.Resolve(to)\n\tcheckErr(token.TransferFrom(fromAddr, toAddr, amount))\n}\n\n// Faucet is distributing foo20 tokens without restriction (unsafe).\n// For a real token faucet, you should take care of setting limits are asking payment.\nfunc Faucet() {\n\tcaller := std.PrevRealm().Addr()\n\tamount := uint64(1_000 * 10_000) // 1k\n\tcheckErr(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.Burn(fromAddr, amount))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn banker.RenderHome()\n\tcase c == 2 \u0026\u0026 parts[0] == \"balance\":\n\t\towner := pusers.AddressOrName(parts[1])\n\t\townerAddr := users.Resolve(owner)\n\t\tbalance := banker.BalanceOf(ownerAddr)\n\t\treturn ufmt.Sprintf(\"%d\\n\", balance)\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n" + }, + { + "name": "foo20_test.gno", + "body": "package foo20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tvar (\n\t\tadmin = pusers.AddressOrName(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\t\talice = pusers.AddressOrName(testutils.TestAddress(\"alice\"))\n\t\tbob = pusers.AddressOrName(testutils.TestAddress(\"bob\"))\n\t)\n\n\ttype test struct {\n\t\tname string\n\t\tbalance uint64\n\t\tfn func() uint64\n\t}\n\n\t// check balances #1.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_000_000_000, func() uint64 { return TotalSupply() }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(admin) }},\n\t\t\t{\"BalanceOf(alice)\", 0, func() uint64 { return BalanceOf(alice) }},\n\t\t\t{\"Allowance(admin, alice)\", 0, func() uint64 { return Allowance(admin, alice) }},\n\t\t\t{\"BalanceOf(bob)\", 0, func() uint64 { return BalanceOf(bob) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tgot := tc.fn()\n\t\t\tuassert.Equal(t, got, tc.balance)\n\t\t}\n\t}\n\n\t// bob uses the faucet.\n\tstd.TestSetOrigCaller(users.Resolve(bob))\n\tFaucet()\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_010_000_000, func() uint64 { return TotalSupply() }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(admin) }},\n\t\t\t{\"BalanceOf(alice)\", 0, func() uint64 { return BalanceOf(alice) }},\n\t\t\t{\"Allowance(admin, alice)\", 0, func() uint64 { return Allowance(admin, alice) }},\n\t\t\t{\"BalanceOf(bob)\", 10_000_000, func() uint64 { return BalanceOf(bob) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tgot := tc.fn()\n\t\t\tuassert.Equal(t, got, tc.balance)\n\t\t}\n\t}\n}\n\nfunc TestErrConditions(t *testing.T) {\n\tvar (\n\t\tadmin = pusers.AddressOrName(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\t\talice = pusers.AddressOrName(testutils.TestAddress(\"alice\"))\n\t\tempty = pusers.AddressOrName(\"\")\n\t)\n\n\ttype test struct {\n\t\tname string\n\t\tmsg string\n\t\tfn func()\n\t}\n\n\tstd.TestSetOrigCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\n\t\t\t{\"Approve(empty, 1))\", \"invalid address\", func() { Approve(empty, 1) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tuassert.PanicsWithMessage(t, tc.msg, tc.fn)\n\t\t\t})\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "foo721", + "path": "gno.land/r/demo/foo721", + "files": [ + { + "name": "foo721.gno", + "body": "package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n" + }, + { + "name": "foo721_test.gno", + "body": "package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "foo20", + "path": "gno.land/r/demo/grc20factory", + "files": [ + { + "name": "grc20factory.gno", + "body": "package foo20\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar instances avl.Tree // symbol -\u003e instance\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PrevRealm().Addr()\n\tNewWithAdmin(name, symbol, decimals, initialMint, faucet, caller)\n}\n\nfunc NewWithAdmin(name, symbol string, decimals uint, initialMint, faucet uint64, admin std.Address) {\n\texists := instances.Has(symbol)\n\tif exists {\n\t\tpanic(\"token already exists\")\n\t}\n\n\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().TransferFrom(from, to, amount))\n}\n\n// faucet.\nfunc Faucet(symbol string) {\n\tinst := mustGetInstance(symbol)\n\tif inst.faucet == 0 {\n\t\tpanic(\"faucet disabled for this token\")\n\t}\n\t// FIXME: add limits?\n\t// FIXME: add payment in gnot?\n\tcaller := std.PrevRealm().Addr()\n\tcheckErr(inst.banker.Mint(caller, inst.faucet))\n}\n\nfunc Mint(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tinst.admin.AssertCallerIsOwner()\n\tcheckErr(inst.banker.Mint(to, amount))\n}\n\nfunc Burn(symbol string, from std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tinst.admin.AssertCallerIsOwner()\n\tcheckErr(inst.banker.Burn(from, amount))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn \"TODO: list existing tokens and admins\"\n\tcase c == 1:\n\t\tsymbol := parts[0]\n\t\tinst := mustGetInstance(symbol)\n\t\treturn inst.banker.RenderHome()\n\tcase c == 3 \u0026\u0026 parts[1] == \"balance\":\n\t\tsymbol := parts[0]\n\t\tinst := mustGetInstance(symbol)\n\t\towner := std.Address(parts[2])\n\t\tbalance := inst.Token().BalanceOf(owner)\n\t\treturn ufmt.Sprintf(\"%d\", balance)\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\nfunc mustGetInstance(symbol string) *instance {\n\tt, exists := instances.Get(symbol)\n\tif !exists {\n\t\tpanic(\"token instance does not exist\")\n\t}\n\treturn t.(*instance)\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n" + }, + { + "name": "grc20factory_test.gno", + "body": "package foo20\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\n\n\ttype test struct {\n\t\tname string\n\t\tbalance uint64\n\t\tfn func() uint64\n\t}\n\n\t// check balances #1.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tif tc.fn() != tc.balance {\n\t\t\t\tt.Errorf(\"%s: have: %d want: %d\", tc.name, tc.fn(), tc.balance)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOrigCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tif tc.fn() != tc.balance {\n\t\t\t\tt.Errorf(\"%s: have: %d want: %d\", tc.name, tc.fn(), tc.balance)\n\t\t\t}\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "groups", + "path": "gno.land/r/demo/groups", + "files": [ + { + "name": "README.md", + "body": "### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./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 \n\n### - create group\n\n ./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 \n\n### - add member\n\n ./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\n\n### - delete member\n\n ./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\n\n### - delete group\n\n ./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\n\n" + }, + { + "name": "group.gno", + "body": "package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n" + }, + { + "name": "groups.gno", + "body": "package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n" + }, + { + "name": "member.gno", + "body": "package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n" + }, + { + "name": "misc.gno", + "body": "package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n" + }, + { + "name": "public.gno", + "body": "package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n" + }, + { + "name": "render.gno", + "body": "package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n" + }, + { + "name": "role.gno", + "body": "package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n" + }, + { + "name": "z_0_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n" + }, + { + "name": "z_0_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n" + }, + { + "name": "z_0_c_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n" + }, + { + "name": "z_1_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n" + }, + { + "name": "z_1_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n" + }, + { + "name": "z_1_c_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n" + }, + { + "name": "z_2_a_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n" + }, + { + "name": "z_2_b_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n" + }, + { + "name": "z_2_d_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n" + }, + { + "name": "z_2_e_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n" + }, + { + "name": "z_2_f_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n" + }, + { + "name": "z_2_g_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "keystore", + "path": "gno.land/r/demo/keystore", + "files": [ + { + "name": "keystore.gno", + "body": "package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.GetOrigCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.GetOrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.GetOrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.GetOrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.GetOrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.GetOrigCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n" + }, + { + "name": "keystore_test.gno", + "body": "package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOrigCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\t\t\tif act != tc.exp {\n\t\t\t\tt.Errorf(\"%v -\u003e '%s', got '%s', wanted '%s'\", tc.ps, p, act, tc.exp)\n\t\t\t}\n\t\t})\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "markdown", + "path": "gno.land/r/demo/markdown_test", + "files": [ + { + "name": "markdown.gno", + "body": "package markdown\n\n// this package can be used to test markdown rendering engines.\n\nfunc Render(path string) string {\n\toutput := `_imported from https://github.com/markedjs/marked/blob/master/docs/demo/quickref.md_\n\nMarkdown Quick Reference\n========================\n\nThis guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*.\n\n[Markdown]: http://daringfireball.net/projects/markdown/\n\n\nSimple Text Formatting\n======================\n\nFirst thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ for bold. ***Three together*** for ___both___.\n\nParagraphs are pretty easy too. Just have a blank line between chunks of text.\n\n\u003e This chunk of text is in a block quote. Its multiple lines will all be\n\u003e indented a bit from the rest of the text.\n\u003e\n\u003e \u003e Multiple levels of block quotes also work.\n\nSometimes you want to include code, such as when you are explaining how ` + \"`\u003ch1\u003e`\" + ` HTML tags work, or maybe you are a programmer and you are discussing ` + \"`someMethod()`\" + `.\n\nIf you want to include code and have new\nlines preserved, indent the line with a tab\nor at least four spaces:\n\n Extra spaces work here too.\n This is also called preformatted text and it is useful for showing examples.\n The text will stay as text, so any *markdown* or \u003cu\u003eHTML\u003c/u\u003e you add will\n not show up formatted. This way you can show markdown examples in a\n markdown document.\n\n\u003e You can also use preformatted text with your blockquotes\n\u003e as long as you add at least five spaces.\n\n\nHeadings\n========\n\nThere are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an \"h1\" style. Three or more hyphens under a line makes it \"h2\" (slightly smaller). You can also use multiple pound symbols (` + \"`#`\" + `) before and after a heading. Pounds after the title are ignored. Here are some examples:\n\nThis is H1\n==========\n\nThis is H2\n----------\n\n# This is H1\n## This is H2\n### This is H3 with some extra pounds ###\n#### You get the idea ####\n##### I don't need extra pounds at the end\n###### H6 is the max\n\n\nLinks\n=====\n\nLet's link to a few sites. First, let's use the bare URL, like \u003chttps://www.github.com\u003e. Great for text, but ugly for HTML.\nNext is an inline link to [Google](https://www.google.com). A little nicer.\nThis is a reference-style link to [Wikipedia] [1].\nLastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly.\n\n[1]: https://www.wikipedia.org\n[Yahoo]: https://www.yahoo.com\n\nTitle attributes may be added to links by adding text after a link.\nThis is the [inline link](https://www.bing.com \"Bing\") with a \"Bing\" title.\nYou can also go to [W3C] [2] and maybe visit a [friend].\n\n[2]: https://w3c.org (The W3C puts out specs for web-based things)\n[Friend]: https://facebook.com \"Facebook!\"\n\nEmail addresses in plain text are not linked: test@example.com.\nEmail addresses wrapped in angle brackets are linked: \u003ctest@example.com\u003e.\nThey are also obfuscated so that email harvesting spam robots hopefully won't get them.\n\n\nLists\n=====\n\n* This is a bulleted list\n* Great for shopping lists\n- You can also use hyphens\n+ Or plus symbols\n\nThe above is an \"unordered\" list. Now, on for a bit of order.\n\n1. Numbered lists are also easy\n2. Just start with a number\n3738762. However, the actual number doesn't matter when converted to HTML.\n1. This will still show up as 4.\n\nYou might want a few advanced lists:\n\n- This top-level list is wrapped in paragraph tags\n- This generates an extra space between each top-level item.\n\n- You do it by adding a blank line\n\n- This nested list also has blank lines between the list items.\n\n- How to create nested lists\n 1. Start your regular list\n 2. Indent nested lists with two spaces\n 3. Further nesting means you should indent with two more spaces\n * This line is indented with four spaces.\n\n- List items can be quite lengthy. You can keep typing and either continue\nthem on the next line with no indentation.\n\n- Alternately, if that looks ugly, you can also\n indent the next line a bit for a prettier look.\n\n- You can put large blocks of text in your list by just indenting with two spaces.\n\n This is formatted the same as code, but you can inspect the HTML\n and find that it's just wrapped in a ` + \"`\u003cp\u003e`\" + ` tag and *won't* be shown\n as preformatted text.\n\n You can keep adding more and more paragraphs to a single\n list item by adding the traditional blank line and then keep\n on indenting the paragraphs with two spaces.\n\n You really only need to indent the first line,\nbut that looks ugly.\n\n- Lists support blockquotes\n\n \u003e Just like this example here. By the way, you can\n \u003e nest lists inside blockquotes!\n \u003e - Fantastic!\n\n- Lists support preformatted text\n\n You just need to indent an additional four spaces.\n\n\nEven More\n=========\n\nHorizontal Rule\n---------------\n\nIf you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.\n\n---\n****************************\n_ _ _ _ _ _ _\n\nThose three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.\n\nImages\n------\n\nImages work exactly like links, but they have exclamation points in front. They work with references and titles too.\n\n![Google Logo](https://www.google.com/images/errors/logo_sm.gif) and ![Happy].\n\n[Happy]: https://wpclipart.com/smiley/happy/simple_colors/smiley_face_simple_green_small.png (\"Smiley face\")\n\n\nInline HTML\n-----------\n\nIf markdown is too limiting, you can just insert your own \u003cstrike\u003ecrazy\u003c/strike\u003e HTML. Span-level HTML \u003cu\u003ecan *still* use markdown\u003c/u\u003e. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.\n\n\u003cdiv style='font-family: \"Comic Sans MS\", \"Comic Sans\", cursive;'\u003e\nIt is a pity, but markdown does **not** work in here for most markdown parsers.\n[Marked] handles it pretty well.\n\u003c/div\u003e`\n\treturn output\n}\n" + }, + { + "name": "markdown_test.gno", + "body": "package markdown\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRender(t *testing.T) {\n\toutput := Render(\"\")\n\tif !strings.Contains(output, \"\\nMarkdown Quick Reference\\n\") {\n\t\tt.Errorf(\"invalid output\")\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "eval", + "path": "gno.land/r/demo/math_eval", + "files": [ + { + "name": "math_eval.gno", + "body": "// eval realm is capable of evaluating 32-bit integer\n// expressions as they would appear in Go. For example:\n// /r/demo/math_eval:(4+12)/2-1+11*15\npackage eval\n\nimport (\n\tevalint32 \"gno.land/p/demo/math_eval/int32\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(p string) string {\n\tif len(p) == 0 {\n\t\treturn `\nevaluates 32-bit integer expressions. for example:\n\t\t\n[(4+12)/2-1+11*15](/r/demo/math_eval:(4+12)/2-1+11*15)\n\n`\n\t}\n\texpr, err := evalint32.Parse(p)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\tres, err := evalint32.Eval(expr, nil)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\n\treturn ufmt.Sprintf(\"%s = %d\", p, res)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "memeland", + "path": "gno.land/r/demo/memeland", + "files": [ + { + "name": "memeland.gno", + "body": "package memeland\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/memeland\"\n)\n\nvar m *memeland.Memeland\n\nfunc init() {\n\tm = memeland.NewMemeland()\n\tm.TransferOwnership(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\")\n}\n\nfunc PostMeme(data string, timestamp int64) string {\n\treturn m.PostMeme(data, timestamp)\n}\n\nfunc Upvote(id string) string {\n\treturn m.Upvote(id)\n}\n\nfunc GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\treturn m.GetPostsInRange(startTimestamp, endTimestamp, page, pageSize, sortBy)\n}\n\nfunc RemovePost(id string) string {\n\treturn m.RemovePost(id)\n}\n\nfunc GetOwner() std.Address {\n\treturn m.Owner()\n}\n\nfunc TransferOwnership(newOwner std.Address) {\n\tif err := m.TransferOwnership(newOwner); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tnumOfMemes := int(m.MemeCounter)\n\tif numOfMemes == 0 {\n\t\treturn \"No memes posted yet! :/\"\n\t}\n\n\t// Default render is get Posts since year 2000 to now\n\treturn m.GetPostsInRange(0, time.Now().Unix(), 1, 10, \"DATE_CREATED\")\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "microblog", + "path": "gno.land/r/demo/microblog", + "files": [ + { + "name": "README.md", + "body": "# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```" + }, + { + "name": "microblog.gno", + "body": "// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n" + }, + { + "name": "microblog_test.gno", + "body": "package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOrigCaller(author1)\n\n\tif Render(\"/wrongpath\") != \"404\" {\n\t\tt.Fatalf(\"rendering not giving 404\")\n\t}\n\tif Render(\"\") == \"404\" {\n\t\tt.Fatalf(\"rendering / should not give 404\")\n\t}\n\tif err := m.NewPost(\"goodbyte, web2\"); err != nil {\n\t\tt.Fatalf(\"could not create post\")\n\t}\n\tif _, err := m.GetPage(author1.String()); err != nil {\n\t\tt.Fatalf(\"silo should exist\")\n\t}\n\tif _, err := m.GetPage(\"no such author\"); err == nil {\n\t\tt.Fatalf(\"silo should not exist\")\n\t}\n\n\tstd.TestSetOrigCaller(author2)\n\n\tif err := m.NewPost(\"hello, web3\"); err != nil {\n\t\tt.Fatalf(\"could not create post\")\n\t}\n\tif err := m.NewPost(\"hello again, web3\"); err != nil {\n\t\tt.Fatalf(\"could not create post\")\n\t}\n\tif err := m.NewPost(\"hi again,\\n web4?\"); err != nil {\n\t\tt.Fatalf(\"could not create post\")\n\t}\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\tif rendering := Render(\"\"); rendering != `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n` {\n\t\tt.Fatalf(\"incorrect rendering /: '%s'\", rendering)\n\t}\n\n\tif rendering := strings.TrimSpace(Render(author1.String())); rendering != `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*` {\n\t\tt.Fatalf(\"incorrect rendering /: '%s'\", rendering)\n\t}\n\n\tif rendering := strings.TrimSpace(Render(author2.String())); rendering != `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*` {\n\t\tt.Fatalf(\"incorrect rendering /: '%s'\", rendering)\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "nft", + "path": "gno.land/r/demo/nft", + "files": [ + { + "name": "README.md", + "body": "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.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.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:gnolang/1)).\n" + }, + { + "name": "nft.gno", + "body": "package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.GetCallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n" + }, + { + "name": "z_0_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n" + }, + { + "name": "z_1_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n" + }, + { + "name": "z_2_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n" + }, + { + "name": "z_3_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n" + }, + { + "name": "z_4_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "releases_example", + "path": "gno.land/r/demo/releases_example", + "files": [ + { + "name": "dummy.gno", + "body": "package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n" + }, + { + "name": "example.gno", + "body": "// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.GetOrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.GetOrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n" + }, + { + "name": "releases0_filetest.gno", + "body": "package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n" + }, + { + "name": "releases1_filetest.gno", + "body": "package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "tamagotchi", + "path": "gno.land/r/demo/tamagotchi", + "files": [ + { + "name": "realm.gno", + "body": "package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi?help\u0026__func=Feed)\n* [Play](/r/demo/tamagotchi?help\u0026__func=Play)\n* [Heal](/r/demo/tamagotchi?help\u0026__func=Heal)\n* [Reset](/r/demo/tamagotchi?help\u0026__func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi?help\u0026__func=Feed)\n// * [Play](/r/demo/tamagotchi?help\u0026__func=Play)\n// * [Heal](/r/demo/tamagotchi?help\u0026__func=Heal)\n// * [Reset](/r/demo/tamagotchi?help\u0026__func=Reset)\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "crossrealm", + "path": "gno.land/r/demo/tests/crossrealm", + "files": [ + { + "name": "crossrealm.gno", + "body": "package crossrealm\n\nimport (\n\t\"gno.land/p/demo/tests/p_crossrealm\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype LocalStruct struct {\n\tA int\n}\n\nfunc (ls *LocalStruct) String() string {\n\treturn ufmt.Sprintf(\"LocalStruct{%d}\", ls.A)\n}\n\n// local is saved locally in this realm\nvar local *LocalStruct\n\nfunc init() {\n\tlocal = \u0026LocalStruct{A: 123}\n}\n\n// Make1 returns a local object wrapped by a p struct\nfunc Make1() *p_crossrealm.Container {\n\treturn \u0026p_crossrealm.Container{\n\t\tA: 1,\n\t\tB: local,\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "tests_foo", + "path": "gno.land/r/demo/tests_foo", + "files": [ + { + "name": "foo.gno", + "body": "package tests_foo\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\n// for testing gno.land/r/demo/tests/interfaces.go\n\ntype FooStringer struct {\n\tFieldA string\n}\n\nfunc (fs *FooStringer) String() string {\n\treturn \"\u0026FooStringer{\" + fs.FieldA + \"}\"\n}\n\nfunc AddFooStringer(fa string) {\n\ttests.AddStringer(\u0026FooStringer{fa})\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "todolistrealm", + "path": "gno.land/r/demo/todolist", + "files": [ + { + "name": "todolist.gno", + "body": "package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n" + }, + { + "name": "todolist_test.gno", + "body": "package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tif tlid != 1 {\n\t\tt.Errorf(\"Expected tlid to be 1, but got %d\", tlid)\n\t}\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tif tdl.Title != title {\n\t\tt.Errorf(\"Expected title to be %s, but got %s\", title, tdl.Title)\n\t}\n\tif tdl.Owner != std.GetOrigCaller() {\n\t\tt.Errorf(\"Expected owner to be %s, but got %s\", std.GetOrigCaller(), tdl.Owner)\n\t}\n\tif len(tdl.GetTasks()) != 0 {\n\t\tt.Errorf(\"Expected no tasks in the todo list, but got %d tasks\", len(tdl.GetTasks()))\n\t}\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tif len(tasks) != 1 {\n\t\tt.Errorf(\"Expected 1 task in the todo list, but got %d tasks\", len(tasks))\n\t}\n\n\tif tasks[0].Title != \"Task 1\" {\n\t\tt.Errorf(\"Expected task title to be 'Task 1', but got '%s'\", tasks[0].Title)\n\t}\n\n\tif tasks[0].Done {\n\t\tt.Errorf(\"Expected task to be not done, but it is marked as done\")\n\t}\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\n\tif !task.Done {\n\t\tt.Errorf(\"Expected task to be done, but it is not marked as done\")\n\t}\n\n\tToggleTaskStatus(1, 0)\n\n\tif task.Done {\n\t\tt.Errorf(\"Expected task to be not done, but it is marked as done\")\n\t}\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\n\tif len(tasks) != 0 {\n\t\tt.Errorf(\"Expected no tasks in the todo list, but got %d tasks\", len(tasks))\n\t}\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\n\tif todolistTree.Size() != 0 {\n\t\tt.Errorf(\"Expected no tasks in the todo list, but got %d tasks\", todolistTree.Size())\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "types", + "path": "gno.land/r/demo/types", + "files": [ + { + "name": "types.gno", + "body": "// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n" + }, + { + "name": "types_test.gno", + "body": "package types\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ui", + "path": "gno.land/r/demo/ui", + "files": [ + { + "name": "ui.gno", + "body": "package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n" + }, + { + "name": "ui_test.gno", + "body": "package ui\n\nimport \"testing\"\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\n\tif got != expected {\n\t\tt.Errorf(\"-\\nexpected: %q\\ngot: %q.\", expected, got)\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "userbook", + "path": "gno.land/r/demo/userbook", + "files": [ + { + "name": "userbook.gno", + "body": "// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PrevRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n" + }, + { + "name": "userbook_test.gno", + "body": "package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOrigCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "wugnot", + "path": "gno.land/r/demo/wugnot", + "files": [ + { + "name": "wugnot.gno", + "body": "package wugnot\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PrevRealm().Addr()\n\tsent := std.GetOrigSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.Mint(caller, uint64(amount)))\n}\n\nfunc Withdraw(amount uint64) {\n\trequire(amount \u003e= wugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d wugnot.\", amount, wugnotMinDeposit))\n\n\tcaller := std.PrevRealm().Addr()\n\tpkgaddr := std.CurrentRealm().Addr()\n\tcallerBal := Token.BalanceOf(caller)\n\trequire(amount \u003c= callerBal, ufmt.Sprintf(\"Insufficient balance: %d available, %d needed.\", callerBal, amount))\n\n\t// send swapped ugnots to qcaller\n\tstdBanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", int64(amount)}}\n\tstdBanker.SendCoins(pkgaddr, caller, send)\n\tcheckErr(banker.Burn(caller, amount))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn banker.RenderHome()\n\tcase c == 2 \u0026\u0026 parts[0] == \"balance\":\n\t\towner := std.Address(parts[1])\n\t\tbalance := Token.BalanceOf(owner)\n\t\treturn ufmt.Sprintf(\"%d\", balance)\n\tdefault:\n\t\treturn \"404\"\n\t}\n}\n\nfunc TotalSupply() uint64 { return Token.TotalSupply() }\n\nfunc BalanceOf(owner pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\treturn Token.BalanceOf(ownerAddr)\n}\n\nfunc Allowance(owner, spender pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\tspenderAddr := users.Resolve(spender)\n\treturn Token.Allowance(ownerAddr, spenderAddr)\n}\n\nfunc Transfer(to pusers.AddressOrName, amount uint64) {\n\ttoAddr := users.Resolve(to)\n\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.Approve(spenderAddr, amount))\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, amount uint64) {\n\tfromAddr := users.Resolve(from)\n\ttoAddr := users.Resolve(to)\n\tcheckErr(Token.TransferFrom(fromAddr, toAddr, amount))\n}\n\nfunc require(condition bool, msg string) {\n\tif !condition {\n\t\tpanic(msg)\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "// PKGPATH: gno.land/r/demo/wugnot_test\npackage wugnot_test\n\nimport (\n\t\"fmt\"\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/wugnot\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"test1\")\n\taddrc = std.DerivePkgAddr(\"gno.land/r/demo/wugnot\")\n\taddrt = std.DerivePkgAddr(\"gno.land/r/demo/wugnot_test\")\n)\n\nfunc main() {\n\tstd.TestSetOrigPkgAddr(addrc)\n\tstd.TestIssueCoins(addrc, std.Coins{{\"ugnot\", 100000001}}) // TODO: remove this\n\n\t// issue ugnots\n\tstd.TestIssueCoins(addr1, std.Coins{{\"ugnot\", 100000001}})\n\n\t// print initial state\n\tprintBalances()\n\t// println(wugnot.Render(\"queues\"))\n\t// println(\"A -\", wugnot.Render(\"\"))\n\n\tstd.TestSetOrigCaller(addr1)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 123_400}}, nil)\n\twugnot.Deposit()\n\tprintBalances()\n\twugnot.Withdraw(4242)\n\tprintBalances()\n}\n\nfunc printBalances() {\n\tprintSingleBalance := func(name string, addr std.Address) {\n\t\twugnotBal := wugnot.BalanceOf(pusers.AddressOrName(addr))\n\t\tstd.TestSetOrigCaller(addr)\n\t\trobanker := std.GetBanker(std.BankerTypeReadonly)\n\t\tcoins := robanker.GetCoins(addr).AmountOf(\"ugnot\")\n\t\tfmt.Printf(\"| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d |\\n\",\n\t\t\tname, addr, wugnotBal, coins)\n\t}\n\tprintln(\"-----------\")\n\tprintSingleBalance(\"wugnot_test\", addrt)\n\tprintSingleBalance(\"wugnot\", addrc)\n\tprintSingleBalance(\"addr1\", addr1)\n\tprintln(\"-----------\")\n}\n\n// Output:\n// -----------\n// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=0 | ugnot=200000000 |\n// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 |\n// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 |\n// -----------\n// -----------\n// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=123400 | ugnot=200000000 |\n// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 |\n// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 |\n// -----------\n// -----------\n// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=119158 | ugnot=200004242 |\n// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=99995759 |\n// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 |\n// -----------\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "gnoblog", + "path": "gno.land/r/gnoland/blog", + "files": [ + { + "name": "admin.gno", + "body": "package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/context\"\n\t\"gno.land/p/gov/proposal\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc DaoAddPost(ctx context.Context, slug, title, body, publicationDate, authors, tags string) {\n\tproposal.AssertContextApprovedByGovDAO(ctx)\n\tcaller := std.DerivePkgAddr(\"gno.land/r/gov/dao\")\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.GetOrigCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n" + }, + { + "name": "gnoblog.gno", + "body": "package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.GetOrigCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n" + }, + { + "name": "gnoblog_test.gno", + "body": "package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOrigCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.GetOrigCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n" + }, + { + "name": "util.gno", + "body": "package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "events", + "path": "gno.land/r/gnoland/events", + "files": [ + { + "name": "events.gno", + "body": "package events\n\nimport (\n\t\"gno.land/p/demo/ui\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nfunc Render(_ string) string {\n\tdom := ui.DOM{Prefix: \"r/gnoland/events:\"}\n\tdom.Title = \"Gno.land Core Team Attends Industry Events \u0026 Meetups\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(upcomingEvents()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(pastEvents()...)\n\n\treturn dom.String()\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.Paragraph(\"If you’re interested in building web3 with us, catch up with gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform.\"),\n\t}\n}\n\nfunc upcomingEvents() ui.Element {\n\treturn ui.Element{\n\t\tui.H2(\"Upcoming Events\"),\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### GopherCon EU\n- Come Meet Us at our Booth\n- Berlin, June 17 - 20, 2024\n\n[Learn More](https://gophercon.eu/)\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### GopherCon US\n- Come Meet Us at our Booth\n- Chicago, July 7 - 10, 2024\n\n[Learn More](https://www.gophercon.com/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Nebular Summit\n- Join our workshop\n- Brussels, July 12 - 13, 2024\n\n[Learn More](https://nebular.builders/)\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n\nfunc pastEvents() ui.Element {\n\treturn ui.Element{\n\t\tui.H2(\"Past Events\"),\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Gno @ Golang Serbia\n\n- **Join the meetup**\n- Belgrade, May 23, 2024\n\n[Learn more](https://gno.land/r/gnoland/blog:p/gnomes-in-serbia)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Intro to Gno Tokyo\n\n- **Join the meetup**\n- Tokyo, April 11, 2024\n\n[Learn more](https://gno.land/r/gnoland/blog:p/gno-tokyo)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Go to Gno Seoul\n\n- **Join the workshop**\n- Seoul, March 23, 2024\n\n[Learn more](https://medium.com/onbloc/go-to-gno-recap-intro-to-the-gno-stack-with-memeland-284a43d7f620)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### GopherCon US\n\n- **Come Meet Us at our Booth**\n- San Diego, September 26 - 29, 2023\n\n[Learn more](https://www.gophercon.com/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### GopherCon EU\n\n- **Come Meet Us at our Booth**\n- Berlin, July 26 - 29, 2023\n\n[Learn more](https://gophercon.eu/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Nebular Summit Gno.land for Developers\n\n- Paris, July 24 - 25, 2023\n- Manfred Touron\n\n[Learn more](https://www.nebular.builders/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### EthCC\n\n- **Come Meet Us at our Booth**\n- Paris, July 17 - 20, 2023\n- Manfred Touron\n\n[Learn more](https://www.ethcc.io/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Eth Seoul\n\n- **The Evolution of Smart Contracts: A Journey into Gno.land**\n- Seoul, June 3, 2023\n- Manfred Touron\n\n[Learn more](https://2023.ethseoul.org/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### BUIDL Asia\n\n- **Proof of Contribution in Gno.land**\n- Seoul, June 6, 2023\n- Manfred Touron\n\n[Learn more](https://www.buidl.asia/)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Game Developer Conference\n\n- **Side Event: Web3 Gaming Apps Powered by Gno**\n- San Francisco, Mach 23, 2023\n- Jae Kwon\n\n[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### EthDenver\n\n- **Side Event: Discover Gno.land**\n- Denver, Feb 24 - Mar 5, 2023\n- Jae Kwon\n\n[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Istanbul Blockchain Week\n\n- Istanbul, Nov 14 - 17, 2022\n- Manfred Touron\n\n[Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Web Summit Buckle Up and Build with Cosmos\n\n- Lisbon, Nov 1 - 4, 2022\n- Manfred Touron\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Cosmoverse\n\n- Medallin, Sept 26 - 28, 2022\n- Manfred Touron\n\n[Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Berlin Blockchain Week Buckle Up and Build with Cosmos\n\n- Berlin, Sept 11 - 18, 2022\n\n[Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n" + }, + { + "name": "events_filetest.gno", + "body": "package main\n\nimport \"gno.land/r/gnoland/events\"\n\nfunc main() {\n\tprintln(events.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Gno.land Core Team Attends Industry Events \u0026 Meetups\n//\n//\n// If you’re interested in building web3 with us, catch up with gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform.\n//\n//\n// ---\n//\n// ## Upcoming Events\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### GopherCon EU\n// - Come Meet Us at our Booth\n// - Berlin, June 17 - 20, 2024\n//\n// [Learn More](https://gophercon.eu/)\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### GopherCon US\n// - Come Meet Us at our Booth\n// - Chicago, July 7 - 10, 2024\n//\n// [Learn More](https://www.gophercon.com/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Nebular Summit\n// - Join our workshop\n// - Brussels, July 12 - 13, 2024\n//\n// [Learn More](https://nebular.builders/)\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n//\n// ---\n//\n// ## Past Events\n//\n// \u003cdiv class=\"columns-3\"\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Gno @ Golang Serbia\n//\n// - **Join the meetup**\n// - Belgrade, May 23, 2024\n//\n// [Learn more](https://gno.land/r/gnoland/blog:p/gnomes-in-serbia)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Intro to Gno Tokyo\n//\n// - **Join the meetup**\n// - Tokyo, April 11, 2024\n//\n// [Learn more](https://gno.land/r/gnoland/blog:p/gno-tokyo)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Go to Gno Seoul\n//\n// - **Join the workshop**\n// - Seoul, March 23, 2024\n//\n// [Learn more](https://medium.com/onbloc/go-to-gno-recap-intro-to-the-gno-stack-with-memeland-284a43d7f620)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### GopherCon US\n//\n// - **Come Meet Us at our Booth**\n// - San Diego, September 26 - 29, 2023\n//\n// [Learn more](https://www.gophercon.com/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### GopherCon EU\n//\n// - **Come Meet Us at our Booth**\n// - Berlin, July 26 - 29, 2023\n//\n// [Learn more](https://gophercon.eu/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Nebular Summit Gno.land for Developers\n//\n// - Paris, July 24 - 25, 2023\n// - Manfred Touron\n//\n// [Learn more](https://www.nebular.builders/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### EthCC\n//\n// - **Come Meet Us at our Booth**\n// - Paris, July 17 - 20, 2023\n// - Manfred Touron\n//\n// [Learn more](https://www.ethcc.io/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Eth Seoul\n//\n// - **The Evolution of Smart Contracts: A Journey into Gno.land**\n// - Seoul, June 3, 2023\n// - Manfred Touron\n//\n// [Learn more](https://2023.ethseoul.org/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### BUIDL Asia\n//\n// - **Proof of Contribution in Gno.land**\n// - Seoul, June 6, 2023\n// - Manfred Touron\n//\n// [Learn more](https://www.buidl.asia/)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Game Developer Conference\n//\n// - **Side Event: Web3 Gaming Apps Powered by Gno**\n// - San Francisco, Mach 23, 2023\n// - Jae Kwon\n//\n// [Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### EthDenver\n//\n// - **Side Event: Discover Gno.land**\n// - Denver, Feb 24 - Mar 5, 2023\n// - Jae Kwon\n//\n// [Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Istanbul Blockchain Week\n//\n// - Istanbul, Nov 14 - 17, 2022\n// - Manfred Touron\n//\n// [Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Web Summit Buckle Up and Build with Cosmos\n//\n// - Lisbon, Nov 1 - 4, 2022\n// - Manfred Touron\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Cosmoverse\n//\n// - Medallin, Sept 26 - 28, 2022\n// - Manfred Touron\n//\n// [Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Berlin Blockchain Week Buckle Up and Build with Cosmos\n//\n// - Berlin, Sept 11 - 18, 2022\n//\n// [Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n//\n// \u003c/main\u003e\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "faucet", + "path": "gno.land/r/gnoland/faucet", + "files": [ + { + "name": "admin.gno", + "body": "package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.GetOrigCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n" + }, + { + "name": "faucet.gno", + "body": "package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.GetOrigCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n" + }, + { + "name": "faucet_test.gno", + "body": "package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOrigCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOrigCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOrigCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n" + }, + { + "name": "z0_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n" + }, + { + "name": "z1_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n" + }, + { + "name": "z2_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n" + }, + { + "name": "z3_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "ghverify", + "path": "gno.land/r/gnoland/ghverify", + "files": [ + { + "name": "README.md", + "body": "# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables." + }, + { + "name": "contract.gno", + "body": "package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.GetOrigCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.GetOrigCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.GetOrigCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn true\n\t})\n\n\treturn result + \"}\"\n}\n" + }, + { + "name": "contract_test.gno", + "body": "package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.GetOrigCaller()\n\tuserAddress := std.Address(testutils.TestAddress(\"user\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(userAddress)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(userAddress) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(userAddress) + `\",\"github_handle\":\"deelawn\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOrigCaller(userAddress)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(userAddress) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tSetOwner(userAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tstd.TestSetOrigCaller(userAddress)\n\tGnorkleEntrypoint(\"ingest,\" + string(userAddress) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(userAddress) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(userAddress)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(userAddress) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(userAddress), address)\n\t}\n}\n" + }, + { + "name": "task.gno", + "body": "package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "home", + "path": "gno.land/r/gnoland/home", + "files": [ + { + "name": "home.gno", + "body": "package home\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/ui\"\n\tblog \"gno.land/r/gnoland/blog\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nfunc Render(_ string) string {\n\tdom := ui.DOM{Prefix: \"r/gnoland/home:\"}\n\tdom.Title = \"Welcome to gno.land\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\n\tdom.Body.Append(ui.Jumbotron(discoverLinks()))\n\n\tdom.Body.Append(\n\t\tui.Columns{3, []ui.Element{\n\t\t\tlastBlogposts(4),\n\t\t\tupcomingEvents(4),\n\t\t\tlastContributions(4),\n\t\t}},\n\t)\n\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(playgroundSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(packageStaffPicks()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(worxDAO()...)\n\tdom.Body.Append(ui.HR{})\n\t// footer\n\tdom.Footer.Append(\n\t\tui.Columns{2, []ui.Element{\n\t\t\tsocialLinks(),\n\t\t\tquoteOfTheBlock(),\n\t\t}},\n\t)\n\n\t// Testnet disclaimer\n\tdom.Footer.Append(\n\t\tui.HR{},\n\t\tui.Bold(\"This is a testnet.\"),\n\t\tui.Text(\"Package names are not guaranteed to be available for production.\"),\n\t)\n\n\treturn dom.String()\n}\n\nfunc lastBlogposts(limit int) ui.Element {\n\tposts := blog.RenderLastPostsWidget(limit)\n\treturn ui.Element{\n\t\tui.H3(\"Latest Blogposts\"),\n\t\tui.Text(posts),\n\t}\n}\n\nfunc lastContributions(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Latest Contributions\"),\n\t\t// TODO: import r/gh to\n\t\tui.Link{Text: \"View latest contributions\", URL: \"https://github.com/gnolang/gno/pulls\"},\n\t}\n}\n\nfunc upcomingEvents(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Upcoming Events\"),\n\t\t// TODO: replace with r/gnoland/events\n\t\tui.Text(\"[View upcoming events](/events)\"),\n\t}\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"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.\"),\n\t\tui.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.\"),\n\t\tui.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.\"),\n\t}\n}\n\nfunc worxDAO() ui.Element {\n\t// WorxDAO\n\t// XXX(manfred): please, let me finish a v0, then we can iterate\n\t// highest level == highest responsibility\n\t// teams are responsible for components they don't owne\n\t// flag : realm maintainers VS facilitators\n\t// teams\n\t// committee of trustees to create the directory\n\t// each directory is a name, has a parent and have groups\n\t// homepage team - blocks aggregating events\n\t// XXX: TODO\n\t/*`\n\t# Directory\n\n\t* gno.land (owned by group)\n\t *\n\t* gnovm\n\t * gnolang (language)\n\t * gnovm\n\t - current challenges / concerns / issues\n\t* tm2\n\t * amino\n\t *\n\n\t## Contributors\n\t``*/\n\treturn ui.Element{\n\t\tui.H3(\"Contributions (WorxDAO \u0026 GoR)\"),\n\t\t// TODO: GoR dashboard + WorxDAO topics\n\t\tui.Text(`coming soon`),\n\t}\n}\n\nfunc quoteOfTheBlock() ui.Element {\n\tquotes := []string{\n\t\t\"Gno is for Truth.\",\n\t\t\"Gno is for Social Coordination.\",\n\t\t\"Gno is _not only_ for DeFi.\",\n\t\t\"Now, you Gno.\",\n\t\t\"Come for the Go, Stay for the Gno.\",\n\t}\n\theight := std.GetHeight()\n\tidx := int(height) % len(quotes)\n\tqotb := quotes[idx]\n\n\treturn ui.Element{\n\t\tui.H3(ufmt.Sprintf(\"Quote of the ~Day~ Block#%d\", height)),\n\t\tui.Quote(qotb),\n\t}\n}\n\nfunc socialLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Socials\"),\n\t\tui.BulletList{\n\t\t\t// XXX: improve UI to support a nice GO api for such links\n\t\t\tui.Text(\"Check out our [community projects](https://github.com/gnolang/awesome-gno)\"),\n\t\t\tui.Text(\"![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\"),\n\t\t\tui.Text(\"![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\"),\n\t\t\tui.Text(\"![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\"),\n\t\t\tui.Text(\"![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\"),\n\t\t},\n\t}\n}\n\nfunc playgroundSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"[Gno Playground](https://play.gno.land)\"),\n\t\tui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting\nwith your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\nexecute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`),\n\t\tui.Paragraph(\"Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\"),\n\t}\n}\n\nfunc packageStaffPicks() ui.Element {\n\t// XXX: make it modifiable from a DAO\n\treturn ui.Element{\n\t\tui.H3(\"Explore New Packages and Realms\"),\n\t\tui.Columns{\n\t\t\t3,\n\t\t\t[]ui.Element{\n\t\t\t\t{\n\t\t\t\t\tui.H4(\"[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/dao\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/faucet\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/home\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/pages\"},\n\t\t\t\t\t},\n\t\t\t\t\tui.H4(\"[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/sys/names\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/rewards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/validators\"},\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/demo/boards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/users\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/banktest\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo20\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo721\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/microblog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/nft\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/types\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/gnoface\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/millipede\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/groups\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"p/demo/avl\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ui\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ufmt\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/merkle\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/bf\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/flow\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/gnode\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc20\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc721\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc discoverLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Learn about gno.land\n\n- [About](/about)\n- [GitHub](https://github.com/gnolang)\n- [Blog](/blog)\n- [Events](/events)\n- Tokenomics (soon)\n- [Partners, Fund, Grants](/partners)\n- [Explore the Ecosystem](/ecosystem)\n- [Careers](https://jobs.lever.co/allinbits?department=Gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Build with Gno\n\n- [Write Gno in the browser](https://play.gno.land)\n- [Read about the Gno Language](/gnolang)\n- [Visit the official documentation](https://docs.gno.land)\n- [Gno by Example](https://gno-by-example.com/)\n- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n- [Get testnet GNOTs](https://faucet.gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Explore the universe\n\n- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n- [Gnoscan](https://gnoscan.io)\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n- Testnet 4 (upcoming)\n- [Testnet 3](https://test3.gno.land/) (archive)\n- [Testnet 2](https://test2.gno.land/) (archive)\n- Testnet Faucet Hub (soon)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n" + }, + { + "name": "home_filetest.gno", + "body": "package main\n\nimport \"gno.land/r/gnoland/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Welcome to gno.land\n//\n// ### 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.\n//\n//\n// 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.\n//\n//\n// 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.\n//\n// \u003cdiv class=\"jumbotron\"\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Learn about gno.land\n//\n// - [About](/about)\n// - [GitHub](https://github.com/gnolang)\n// - [Blog](/blog)\n// - [Events](/events)\n// - Tokenomics (soon)\n// - [Partners, Fund, Grants](/partners)\n// - [Explore the Ecosystem](/ecosystem)\n// - [Careers](https://jobs.lever.co/allinbits?department=Gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Build with Gno\n//\n// - [Write Gno in the browser](https://play.gno.land)\n// - [Read about the Gno Language](/gnolang)\n// - [Visit the official documentation](https://docs.gno.land)\n// - [Gno by Example](https://gno-by-example.com/)\n// - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n// - [Get testnet GNOTs](https://faucet.gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Explore the universe\n//\n// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n// - [Gnoscan](https://gnoscan.io)\n// - [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n// - Testnet 4 (upcoming)\n// - [Testnet 3](https://test3.gno.land/) (archive)\n// - [Testnet 2](https://test2.gno.land/) (archive)\n// - Testnet Faucet Hub (soon)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n// \u003c/div\u003e\u003c!-- /jumbotron --\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Blogposts\n//\n// No posts.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Upcoming Events\n//\n// [View upcoming events](/events)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Contributions\n//\n// [View latest contributions](https://github.com/gnolang/gno/pulls)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### [Gno Playground](https://play.gno.land)\n//\n//\n// Gno Playground is a web application designed for building, running, testing, and interacting\n// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\n// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.\n//\n//\n// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\n//\n//\n// ---\n//\n// ### Explore New Packages and Realms\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\n//\n// - [r/gnoland/blog](r/gnoland/blog)\n// - [r/gnoland/dao](r/gnoland/dao)\n// - [r/gnoland/faucet](r/gnoland/faucet)\n// - [r/gnoland/home](r/gnoland/home)\n// - [r/gnoland/pages](r/gnoland/pages)\n//\n// #### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\n//\n// - [r/sys/names](r/sys/names)\n// - [r/sys/rewards](r/sys/rewards)\n// - [r/sys/validators](r/sys/validators)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\n//\n// - [r/demo/boards](r/demo/boards)\n// - [r/demo/users](r/demo/users)\n// - [r/demo/banktest](r/demo/banktest)\n// - [r/demo/foo20](r/demo/foo20)\n// - [r/demo/foo721](r/demo/foo721)\n// - [r/demo/microblog](r/demo/microblog)\n// - [r/demo/nft](r/demo/nft)\n// - [r/demo/types](r/demo/types)\n// - [r/demo/art/gnoface](r/demo/art/gnoface)\n// - [r/demo/art/millipede](r/demo/art/millipede)\n// - [r/demo/groups](r/demo/groups)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\n//\n// - [p/demo/avl](p/demo/avl)\n// - [p/demo/blog](p/demo/blog)\n// - [p/demo/ui](p/demo/ui)\n// - [p/demo/ufmt](p/demo/ufmt)\n// - [p/demo/merkle](p/demo/merkle)\n// - [p/demo/bf](p/demo/bf)\n// - [p/demo/flow](p/demo/flow)\n// - [p/demo/gnode](p/demo/gnode)\n// - [p/demo/grc/grc20](p/demo/grc/grc20)\n// - [p/demo/grc/grc721](p/demo/grc/grc721)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### Contributions (WorxDAO \u0026 GoR)\n//\n// coming soon\n//\n// ---\n//\n//\n// \u003cdiv class=\"columns-2\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Socials\n//\n// - Check out our [community projects](https://github.com/gnolang/awesome-gno)\n// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\n// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\n// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\n// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Quote of the ~Day~ Block#123\n//\n// \u003e Now, you Gno.\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-2 --\u003e\n//\n//\n// ---\n//\n// **This is a testnet.**\n// Package names are not guaranteed to be available for production.\n//\n// \u003c/main\u003e\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "monit", + "path": "gno.land/r/gnoland/monit", + "files": [ + { + "name": "monit.gno", + "body": "// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PrevRealm().Addr()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PrevRealm().Addr()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n" + }, + { + "name": "monit_test.gno", + "body": "package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\t*/\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "gnopages", + "path": "gno.land/r/gnoland/pages", + "files": [ + { + "name": "admin.gno", + "body": "package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.GetOrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n" + }, + { + "name": "page_about.gno", + "body": "package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"Gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\nGno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code \nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent, \nauditable code that anyone can inspect and reuse.\n\nGno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive \nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes \nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that \noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and \nalignment. \n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for \nfuture generations with censorship-resistant tools that improve their understanding of the world. \n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n" + }, + { + "name": "page_ecosystem.gno", + "body": "package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover Gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your \nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo. \n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. \nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to \ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration.\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an \nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player \nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n" + }, + { + "name": "page_gnolang.gno", + "body": "package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for Gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[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.\n\nUnder 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).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe 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.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo 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.\n\nAnother 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.\n\n## Gno vs Solidity\n\nThe 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.\n\nSolidity 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.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno 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.\n\nUsing 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.\n\nThe 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.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n" + }, + { + "name": "page_gor.gno", + "body": "package gnopages\n\nfunc init() {\n\tpath := \"gor\"\n\ttitle := \"Game of Realms - A Contest For The Best Contributors\"\n\t// XXX: description := \"Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the gno.land platform with a 133,700 ATOM prize pool.\"\n\tbody := `\n\n\u003cdiv class=\"jumbotron\"\u003e\n\n### Game of Realms\n\nThe first high-stakes contest will see participants compete for tiered membership to co-own the gno.land blockchain. A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world.\n\n\u003c/div\u003e\u003c!-- end jumbotron--\u003e\n\nThe competition is currently in phase one – for advanced developers only.\n\nOnce the necessary tools to start phase two are ready, we’ll open up the competition to newer devs and non-technical contributors.\n\nIf you want to stack ATOM rewards and play a key role in the success of gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today.\n\n\u003cdiv\u003e\n\n\u003cdiv role=\"tablist\" aria-labelledby=\"tablist-1\" class=\"tabs\"\u003e\n\u003cdiv class=\"columns-2\"\u003e\n\u003cdiv\u003e\n\n## Phase I. (ongoing)\n\n- \u003cbutton id=\"tab-1\" type=\"button\" role=\"tab\" aria-selected=\"true\" aria-controls=\"tabpanel-1\"\u003eEvaluation DAO (30%)\u003c/button\u003e\n\n- \u003cbutton id=\"tab-2\" type=\"button\" role=\"tab\" aria-selected=\"false\" aria-controls=\"tabpanel-2\" tabindex=\"-1\"\u003eTutorials (80%)\u003c/button\u003e\n\n- \u003cbutton id=\"tab-3\" type=\"button\" role=\"tab\" aria-selected=\"false\" aria-controls=\"tabpanel-3\" tabindex=\"-1\"\u003eGovernance Module (25%)\u003c/button\u003e\n\n\u003c/div\u003e\n\u003cdiv\u003e\n\n## Phase II. (Locked)\n\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"jumbotron\"\u003e\n\n\u003cdiv id=\"tabpanel-1\" role=\"tabpanel\" tabindex=\"0\" aria-labelledby=\"tab-1\" class=\"\"\u003e\n\n## Evaluation DAO\n\nThis complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the gno.land platform are fairly rewarded.\n\n\u003cdiv class=\"accordion gor-accordion\"\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger is-muted\" aria-controls=\"acc-1\" id=\"accpanel-1\"\u003e \u0026#9745; \u003cspan class=\"is-finished\"\u003eClarifying this issue\u003c/span\u003e — [100%\u0026nbsp;completed] \u003c/button\u003e\n\n\u003cdiv id=\"acc-1\" role=\"region\" aria-labelledby=\"accpanel-1\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"true\" class=\"accordion-trigger\" aria-controls=\"acc-2\" id=\"accpanel-2\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eRetrospectives \u0026 investigations\u003c/span\u003e — [20%\u0026nbsp;In progress]\u003c/button\u003e\n\n\u003cdiv id=\"acc-2\" role=\"region\" aria-labelledby=\"accpanel-2\" class=\"accordion-panel\"\u003e\n\nGame of Realms participants and core contributors are still in discussions, proposing additional ideas, and seeing how the proposal for the Evaluation DAO evolves over time.\n\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"true\" class=\"accordion-trigger\" aria-controls=\"acc-3\" id=\"accpanel-3\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eHuman specs — definitions, rules, examples\u003c/span\u003e — [20%\u0026nbsp;In progress]\u003c/button\u003e\n\n\u003cdiv id=\"acc-3\" role=\"region\" aria-labelledby=\"accpanel-3\" class=\"accordion-panel\"\u003e\n\nSee [GitHub issue 519](https://github.com/gnolang/gno/issues/519) for the most up-to-date discussion so far on how voting should work for the DAO, what the responsibilities are, how to join, etc.\n\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-4\" id=\"accpanel-4\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eTechnical specs and interfaces\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-4\" role=\"region\" aria-labelledby=\"accpanel-4\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-5\" id=\"accpanel-5\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eImplementation\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-5\" role=\"region\" aria-labelledby=\"accpanel-5\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-6\" id=\"accpanel-6\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eDocumentation\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-6\" role=\"region\" aria-labelledby=\"accpanel-6\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-7\" id=\"accpanel-7\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eBootstrapping plan\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-7\" role=\"region\" aria-labelledby=\"accpanel-7\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"tabpanel-2\" role=\"tabpanel\" tabindex=\"0\" aria-labelledby=\"tab-2\" class=\"\"\u003e\n\n## Tutorials\n\nTo progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to gno.land.\n\n\u003cdiv class=\"accordion gor-accordion\"\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger is-muted\" aria-controls=\"acc-8\" id=\"accpanel-8\"\u003e \u0026#9745; \u003cspan class=\"is-finished\"\u003eClarifying this issue\u003c/span\u003e — [100%\u0026nbsp;completed] \u003c/button\u003e\n\n\u003cdiv id=\"acc-8\" role=\"region\" aria-labelledby=\"accpanel-8\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger is-muted\" aria-controls=\"acc-9\" id=\"accpanel-9\"\u003e \u0026#9745; \u003cspan class=\"is-finished\"\u003eRetrospectives \u0026 investigations\u003c/span\u003e — [100%\u0026nbsp;completed]\u003c/button\u003e\n\n\u003cdiv id=\"acc-9\" role=\"region\" aria-labelledby=\"accpanel-9\" class=\"accordion-panel is-muted is-hidden\"\u003e\n\nHow to create, present, and house the tutorials has been established with productive discussions between core contributors and external participants.\n\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger is-muted\" aria-controls=\"acc-10\" id=\"accpanel-10\"\u003e \u0026#9745; \u003cspan class=\"is-finished\"\u003eHuman specs — definitions, rules, examples\u003c/span\u003e — [100%\u0026nbsp;completed]\u003c/button\u003e\n\n\u003cdiv id=\"acc-10\" role=\"region\" aria-labelledby=\"accpanel-10\" class=\"accordion-panel is-muted is-hidden\"\u003e\n\nWe followed a collaborative approach to defining the scope of the work and creating a series of tutorials and videos, Gno by Example, to explain core concepts and show newcomers how to write in the Gno programming language. Gno docs and tutorials will be community-run so that anyone can contribute. Onbloc’s [developer portal](https://docs.onbloc.xyz/) is an excellent onramp to gno.land currently. We will soon be releasing a documentation instance to house all tutorials.\n\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger is-muted\" aria-controls=\"acc-11\" id=\"accpanel-11\"\u003e \u0026#9745; \u003cspan class=\"is-finished\"\u003eTechnical specs and interfaces\u003c/span\u003e — [100%\u0026nbsp;completed]\u003c/button\u003e\n\n\u003cdiv id=\"acc-11\" role=\"region\" aria-labelledby=\"accpanel-11\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-12\" id=\"accpanel-12\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eImplementation\u003c/span\u003e — [80%\u0026nbsp;In progress]\u003c/button\u003e\n\n\u003cdiv id=\"acc-12\" role=\"region\" aria-labelledby=\"accpanel-12\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-13\" id=\"accpanel-13\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eBootstrapping plan\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-13\" role=\"region\" aria-labelledby=\"accpanel-13\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"tabpanel-3\" role=\"tabpanel\" tabindex=\"0\" aria-labelledby=\"tab-3\" class=\"\"\u003e\n\n## Governance Module\n\nCan you define and implement a governance contract suite that rivals existing ones, such as the Cosmos Hub? Show us how! We’re looking for the fairest and most efficient governance solution possible.\n\n\u003cdiv class=\"accordion gor-accordion\"\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger is-muted\" aria-controls=\"acc-14\" id=\"accpanel-14\"\u003e \u0026#9745; \u003cspan class=\"is-finished\"\u003eClarifying this issue\u003c/span\u003e — [100%\u0026nbsp;completed] \u003c/button\u003e\n\n\u003cdiv id=\"acc-14\" role=\"region\" aria-labelledby=\"accpanel-14\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"true\" class=\"accordion-trigger\" aria-controls=\"acc-15\" id=\"accpanel-15\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eRetrospectives \u0026 investigations\u003c/span\u003e — [60%\u0026nbsp;In progress]\u003c/button\u003e\n\n\u003cdiv id=\"acc-15\" role=\"region\" aria-labelledby=\"accpanel-15\" class=\"accordion-panel\"\u003e\n\nGame of Realms participants and core contributors have made significant progress teaming up to complete this challenge but discussions and additional ideas are still ongoing.\n\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-16\" id=\"accpanel-16\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eHuman specs — definitions, rules, examples\u003c/span\u003e — [20%\u0026nbsp;In progress]\u003c/button\u003e\n\n\u003cdiv id=\"acc-16\" role=\"region\" aria-labelledby=\"accpanel-16\" class=\"accordion-panel is-muted is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-17\" id=\"accpanel-17\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eTechnical specs and interfaces\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-17\" role=\"region\" aria-labelledby=\"accpanel-17\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-18\" id=\"accpanel-18\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eImplementation\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-18\" role=\"region\" aria-labelledby=\"accpanel-18\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-19\" id=\"accpanel-19\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eDocumentation\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-19\" role=\"region\" aria-labelledby=\"accpanel-19\" class=\"accordion-panel is-hidden\"\u003e\u003c/div\u003e\n\n\u003cbutton type=\"button\" aria-expanded=\"false\" class=\"accordion-trigger\" aria-controls=\"acc-20\" id=\"accpanel-20\"\u003e \u0026#9744; \u003cspan class=\"is-underline\"\u003eBootstrapping plan\u003c/span\u003e — [0%\u0026nbsp;Stand-by]\u003c/button\u003e\n\n\u003cdiv id=\"acc-20\" role=\"region\" aria-labelledby=\"accpanel-20\" class=\"accordion-panel is-hidden\"\u003e\n\u003c/div\u003e\n\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n## Register Now\n\n\u003c!-- mailchimp --\u003e\n\u003cdiv id=\"mc_embed_signup\"\u003e\n\u003cform action=\"https://land.us18.list-manage.com/subscribe/post?u=8befe3303cf82796d2c1a1aff\u0026amp;id=5499ca154b\u0026amp;f_id=008d70e7f0\" method=\"post\" id=\"mc-embedded-subscribe-form\" name=\"mc-embedded-subscribe-form\" class=\"validate\" target=\"_self\"\u003e\n \u003clabel for=\"mce-EMAIL\"\u003eLeave your email to be informed about Game of Realms\u003c/label\u003e\n \u003cdiv id=\"mc_embed_signup_scroll\"\u003e\n \t\u003cdiv class=\"mc-field-group\"\u003e\n \t\t\u003cinput type=\"email\" value=\"\" name=\"EMAIL\" class=\"required email\" id=\"mce-EMAIL\" placeholder=\"Type your email here\" required\u003e\n \t\t\u003cinput type=\"submit\" value=\"Subscribe\" name=\"subscribe\" id=\"mc-embedded-subscribe\" class=\"button\"\u003e\n \t\u003c/div\u003e\n \t\u003cdiv hidden=\"true\"\u003e\u003cinput type=\"hidden\" name=\"tags\" value=\"2525514\"\u003e\u003c/div\u003e\n \t\u003cdiv id=\"mce-responses\" class=\"clear\"\u003e\n \t\t\u003cdiv class=\"response\" id=\"mce-error-response\" style=\"display:none\"\u003e\u003c/div\u003e\n \t\t\u003cdiv class=\"response\" id=\"mce-success-response\" style=\"display:none\"\u003e\u003c/div\u003e\n \t\u003c/div\u003e\n \t\u003c!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups--\u003e\n \t\u003cdiv style=\"position: absolute; left: -5000px;\" aria-hidden=\"true\"\u003e\u003cinput type=\"text\" name=\"b_8befe3303cf82796d2c1a1aff_5499ca154b\" tabindex=\"-1\" value=\"\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n\u003c/form\u003e\n\u003c/div\u003e\n\u003c!-- /mailchimp --\u003e\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:26Z\", nil, nil)\n}\n" + }, + { + "name": "page_license.gno", + "body": "package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n" + }, + { + "name": "page_partners.gno", + "body": "package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n" + }, + { + "name": "page_start.gno", + "body": "package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n" + }, + { + "name": "page_testnets.gno", + "body": "package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"Gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet \n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- test4.gno.land (upcoming)\n- _[test3.gno.land](https://test3.gno.land) (latest)_\n- _[test2.gno.land](https://test2.gno.land) (archive)_\n- _[test1.gno.land](https://test1.gno.land) (archive)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n" + }, + { + "name": "page_tokenomics.gno", + "body": "package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"Gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n" + }, + { + "name": "pages.gno", + "body": "package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n" + }, + { + "name": "pages_test.gno", + "body": "package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/gor\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"Gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"Gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n" + }, + { + "name": "util.gno", + "body": "package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "govdao", + "path": "gno.land/r/gov/dao", + "files": [ + { + "name": "dao.gno", + "body": "package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpproposal \"gno.land/p/gov/proposal\"\n)\n\nvar (\n\tproposals = make([]*proposal, 0)\n\tmembers = make([]std.Address, 0) // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs\n)\n\nconst (\n\tmsgMissingExecutor = \"missing proposal executor\"\n\tmsgPropExecuted = \"prop already executed\"\n\tmsgPropExpired = \"prop is expired\"\n\tmsgPropInactive = \"prop is not active anymore\"\n\tmsgPropActive = \"prop is still active\"\n\tmsgPropNotAccepted = \"prop is not accepted\"\n\n\tmsgCallerNotAMember = \"caller is not member of govdao\"\n\tmsgProposalNotFound = \"proposal not found\"\n)\n\nfunc init() {\n\t// Prepare the initial members\n\tset := []std.Address{\n\t\t/* GNO CORE */\n\t\t\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\", // Jae\n\t\t\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\", // Manfred\n\t\t\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\", // Milos\n\t\t\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\", // Nemanja\n\t\t\"g17p2kkyy9lp2z3ecw4psssk357vxp20afnyl00d\", // Petar\n\t\t\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\", // Marc\n\t\t\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\", // Antonio\n\t\t\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\", // Guilhem\n\t\t\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\", // Ray\n\t\t\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\", // Maxwell\n\t\t\"g1acn3xssksatydd0fcuslvgmjyw0fzkjdhusddg\", // Dylan\n\t\t\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\", // Morgan\n\t\t\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\", // Sergio\n\t\t\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\", // Leon\n\n\t\t/* GNO DEVX */\n\t\t\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\", // Ilker\n\t\t\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\", // Jerónimo\n\t\t\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\", // Denis\n\t\t\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\", // Danny\n\t\t\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\", // Michelle\n\t\t\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\", // Alan\n\t\t\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\", // Salvo\n\t\t\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\", // Alexis\n\n\t\t/* ONBLOC */\n\t\t\"g17m8hlvm3k0agngz8vw29etpcjd2yvcel6pvt3k\", // Andrew\n\t\t\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\", // Dongwon\n\t\t\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\", // Blake\n\t\t\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\", // Jinwoo\n\t\t\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\", // ByeongJun\n\n\t\t/* TERITORI */\n\t\t\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\", // Norman\n\n\t\t/* BERTY */\n\t\t\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\", // Rémi\n\n\t\t/* FLIPPANDO / ZENTASKTIC */\n\t\t\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\", // Dragos\n\t}\n\n\t// Save the members\n\tmembers = append(members, set...)\n}\n\ntype proposal struct {\n\tauthor std.Address\n\tcomment string\n\texecutor pproposal.Executor\n\tvoter Voter\n\texecuted bool\n\tvoters []std.Address // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs.\n}\n\nfunc (p proposal) Status() Status {\n\tif p.executor.IsExpired() {\n\t\treturn Expired\n\t}\n\n\tif p.executor.IsDone() {\n\t\treturn Succeeded\n\t}\n\n\tif !p.voter.IsFinished(members) {\n\t\treturn Active\n\t}\n\n\tif p.voter.IsAccepted(members) {\n\t\treturn Accepted\n\t}\n\n\treturn NotAccepted\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(comment string, executor pproposal.Executor) int {\n\t// XXX: require payment?\n\tif executor == nil {\n\t\tpanic(msgMissingExecutor)\n\t}\n\tcaller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE!\n\tAssertIsMember(caller)\n\n\tprop := \u0026proposal{\n\t\tcomment: comment,\n\t\texecutor: executor,\n\t\tauthor: caller,\n\t\tvoter: NewPercentageVoter(66), // at least 2/3 must say yes\n\t}\n\n\tproposals = append(proposals, prop)\n\n\treturn len(proposals) - 1\n}\n\nfunc VoteOnProposal(idx int, option string) {\n\tassertProposalExists(idx)\n\tcaller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE!\n\tAssertIsMember(caller)\n\n\tprop := getProposal(idx)\n\n\tif prop.executed {\n\t\tpanic(msgPropExecuted)\n\t}\n\n\tif prop.executor.IsExpired() {\n\t\tpanic(msgPropExpired)\n\t}\n\n\tif prop.voter.IsFinished(members) {\n\t\tpanic(msgPropInactive)\n\t}\n\n\tprop.voter.Vote(members, caller, option)\n}\n\nfunc ExecuteProposal(idx int) {\n\tassertProposalExists(idx)\n\tprop := getProposal(idx)\n\n\tif prop.executed {\n\t\tpanic(msgPropExecuted)\n\t}\n\n\tif prop.executor.IsExpired() {\n\t\tpanic(msgPropExpired)\n\t}\n\n\tif !prop.voter.IsFinished(members) {\n\t\tpanic(msgPropActive)\n\t}\n\n\tif !prop.voter.IsAccepted(members) {\n\t\tpanic(msgPropNotAccepted)\n\t}\n\n\tprop.executor.Execute()\n\tprop.voters = members\n\tprop.executed = true\n}\n\nfunc IsMember(addr std.Address) bool {\n\tif len(members) == 0 { // special case for initial execution\n\t\treturn true\n\t}\n\n\tfor _, v := range members {\n\t\tif v == addr {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc AssertIsMember(addr std.Address) {\n\tif !IsMember(addr) {\n\t\tpanic(msgCallerNotAMember)\n\t}\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tif len(proposals) == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\t\tfor idx, prop := range proposals {\n\t\t\toutput += ufmt.Sprintf(\"- [%d](/r/gov/dao:%d) - %s (**%s**)(by %s)\\n\", idx, idx, prop.comment, string(prop.Status()), prop.author)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// else display the proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404\"\n\t}\n\n\tif !proposalExists(idx) {\n\t\treturn \"404\"\n\t}\n\tprop := getProposal(idx)\n\n\tvs := members\n\tif prop.executed {\n\t\tvs = prop.voters\n\t}\n\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.comment\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", string(prop.Status()))\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Voting status: %s\", prop.voter.Status(vs))\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Author: %s\", string(prop.author))\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n\nfunc getProposal(idx int) *proposal {\n\tif idx \u003e len(proposals)-1 {\n\t\tpanic(msgProposalNotFound)\n\t}\n\n\treturn proposals[idx]\n}\n\nfunc proposalExists(idx int) bool {\n\treturn idx \u003e= 0 \u0026\u0026 idx \u003c= len(proposals)\n}\n\nfunc assertProposalExists(idx int) {\n\tif !proposalExists(idx) {\n\t\tpanic(\"invalid proposal id\")\n\t}\n}\n" + }, + { + "name": "dao_test.gno", + "body": "package govdao\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n\tpproposal \"gno.land/p/gov/proposal\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tu1 := testutils.TestAddress(\"u1\")\n\tu2 := testutils.TestAddress(\"u2\")\n\tu3 := testutils.TestAddress(\"u3\")\n\n\tmembers = append(members, u1)\n\tmembers = append(members, u2)\n\tmembers = append(members, u3)\n\n\tnu1 := testutils.TestAddress(\"random1\")\n\n\tout := Render(\"\")\n\n\texpected := \"No proposals found :(\"\n\turequire.Equal(t, expected, out)\n\n\tvar called bool\n\tex := pproposal.NewExecutor(func() error {\n\t\tcalled = true\n\t\treturn nil\n\t})\n\n\tstd.TestSetOrigCaller(u1)\n\tpid := Propose(\"dummy proposal\", ex)\n\n\t// try to vote not being a member\n\tstd.TestSetOrigCaller(nu1)\n\n\turequire.PanicsWithMessage(t, msgCallerNotAMember, func() {\n\t\tVoteOnProposal(pid, \"YES\")\n\t})\n\n\t// try to vote several times\n\tstd.TestSetOrigCaller(u1)\n\turequire.NotPanics(t, func() {\n\t\tVoteOnProposal(pid, \"YES\")\n\t})\n\turequire.PanicsWithMessage(t, msgAlreadyVoted, func() {\n\t\tVoteOnProposal(pid, \"YES\")\n\t})\n\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: active\n\nVoting status: YES: 1, NO: 0, percent: 33, members: 3\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n\tstd.TestSetOrigCaller(u2)\n\turequire.PanicsWithMessage(t, msgWrongVotingValue, func() {\n\t\tVoteOnProposal(pid, \"INCORRECT\")\n\t})\n\turequire.NotPanics(t, func() {\n\t\tVoteOnProposal(pid, \"NO\")\n\t})\n\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: active\n\nVoting status: YES: 1, NO: 1, percent: 33, members: 3\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n\tstd.TestSetOrigCaller(u3)\n\turequire.NotPanics(t, func() {\n\t\tVoteOnProposal(pid, \"YES\")\n\t})\n\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: accepted\n\nVoting status: YES: 2, NO: 1, percent: 66, members: 3\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n\t// Add a new member, so non-executed proposals will change the voting status\n\tu4 := testutils.TestAddress(\"u4\")\n\tmembers = append(members, u4)\n\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: active\n\nVoting status: YES: 2, NO: 1, percent: 50, members: 4\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n\tstd.TestSetOrigCaller(u4)\n\turequire.NotPanics(t, func() {\n\t\tVoteOnProposal(pid, \"YES\")\n\t})\n\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: accepted\n\nVoting status: YES: 3, NO: 1, percent: 75, members: 4\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n\tExecuteProposal(pid)\n\turequire.True(t, called)\n\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: succeeded\n\nVoting status: YES: 3, NO: 1, percent: 75, members: 4\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n\t// Add a new member and try to vote an already executed proposal\n\tu5 := testutils.TestAddress(\"u5\")\n\tmembers = append(members, u5)\n\tstd.TestSetOrigCaller(u5)\n\turequire.PanicsWithMessage(t, msgPropExecuted, func() {\n\t\tExecuteProposal(pid)\n\t})\n\n\t// even if we added a new member the executed proposal is showing correctly the members that voted on it\n\tout = Render(\"0\")\n\texpected = `# Prop #0\n\ndummy proposal\n\nStatus: succeeded\n\nVoting status: YES: 3, NO: 1, percent: 75, members: 4\n\nAuthor: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr\n\n`\n\n\turequire.Equal(t, expected, out)\n\n}\n" + }, + { + "name": "memberset.gno", + "body": "package govdao\n\nimport (\n\t\"std\"\n\n\tpproposal \"gno.land/p/gov/proposal\"\n)\n\nconst daoPkgPath = \"gno.land/r/gov/dao\"\n\nconst (\n\terrNoChangesProposed = \"no set changes proposed\"\n\terrNotGovDAO = \"caller not govdao executor\"\n)\n\nfunc NewPropExecutor(changesFn func() []std.Address) pproposal.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\t// Make sure the GovDAO executor runs the valset changes\n\t\tassertGovDAOCaller()\n\n\t\tfor _, addr := range changesFn() {\n\t\t\tmembers = append(members, addr)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn pproposal.NewExecutor(callback)\n}\n\n// assertGovDAOCaller verifies the caller is the GovDAO executor\nfunc assertGovDAOCaller() {\n\tif std.CurrentRealm().PkgPath() != daoPkgPath {\n\t\tpanic(errNotGovDAO)\n\t}\n}\n" + }, + { + "name": "prop1_filetest.gno", + "body": "// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao\"\n\t\"gno.land/r/sys/validators\"\n)\n\nconst daoPkgPath = \"gno.land/r/gov/dao\"\n\nfunc init() {\n\tmembersFn := func() []std.Address {\n\t\treturn []std.Address{\n\t\t\tstd.Address(\"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"),\n\t\t}\n\t}\n\n\tmExec := govdao.NewPropExecutor(membersFn)\n\n\tcomment := \"adding someone to vote\"\n\tid := govdao.Propose(comment, mExec)\n\tgovdao.ExecuteProposal(id)\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal.\n\t// XXX: payment\n\tcomment = \"manual valset changes proposal example\"\n\tgovdao.Propose(comment, executor)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"1\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(1, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"1\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(1)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"1\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [0](/r/gov/dao:0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n// - [1](/r/gov/dao:1) - manual valset changes proposal example (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #1\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting status: YES: 0, NO: 0, percent: 0, members: 1\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n//\n// --\n// --\n// # Prop #1\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting status: YES: 1, NO: 0, percent: 100, members: 1\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #1\n//\n// manual valset changes proposal example\n//\n// Status: succeeded\n//\n// Voting status: YES: 1, NO: 0, percent: 100, members: 1\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n" + }, + { + "name": "prop2_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/context\"\n\t\"gno.land/p/gov/proposal\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao\"\n)\n\nfunc init() {\n\tmembersFn := func() []std.Address {\n\t\treturn []std.Address{\n\t\t\tstd.Address(\"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"),\n\t\t}\n\t}\n\n\tmExec := govdao.NewPropExecutor(membersFn)\n\n\tcomment := \"adding someone to vote\"\n\n\tid := govdao.Propose(comment, mExec)\n\n\tgovdao.ExecuteProposal(id)\n\n\texecutor := proposal.NewCtxExecutor(func(ctx context.Context) error {\n\t\tgnoblog.DaoAddPost(\n\t\t\tctx,\n\t\t\t\"hello-from-govdao\", // slug\n\t\t\t\"Hello from GovDAO!\", // title\n\t\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\t\ttime.Now().Format(time.RFC3339), // publidation date\n\t\t\t\"moul\", // authors\n\t\t\t\"govdao,example\", // tags\n\t\t)\n\t\treturn nil\n\t})\n\n\t// Create a proposal.\n\t// XXX: payment\n\tcomment = \"post a new blogpost about govdao\"\n\tgovdao.Propose(comment, executor)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"1\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(1, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"1\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(1)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"1\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [0](/r/gov/dao:0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n// - [1](/r/gov/dao:1) - post a new blogpost about govdao (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #1\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting status: YES: 0, NO: 0, percent: 0, members: 1\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n//\n// --\n// --\n// # Prop #1\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting status: YES: 1, NO: 0, percent: 100, members: 1\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #1\n//\n// post a new blogpost about govdao\n//\n// Status: succeeded\n//\n// Voting status: YES: 1, NO: 0, percent: 100, members: 1\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n" + }, + { + "name": "types.gno", + "body": "package govdao\n\nimport (\n\t\"std\"\n)\n\n// Status enum.\ntype Status string\n\nvar (\n\tAccepted Status = \"accepted\"\n\tActive Status = \"active\"\n\tNotAccepted Status = \"not accepted\"\n\tExpired Status = \"expired\"\n\tSucceeded Status = \"succeeded\"\n)\n\n// Voter defines the needed methods for a voting system\ntype Voter interface {\n\n\t// IsAccepted indicates if the voting process had been accepted\n\tIsAccepted(voters []std.Address) bool\n\n\t// IsFinished indicates if the voting process is finished\n\tIsFinished(voters []std.Address) bool\n\n\t// Vote adds a new vote to the voting system\n\tVote(voters []std.Address, caller std.Address, flag string)\n\n\t// Status returns a human friendly string describing how the voting process is going\n\tStatus(voters []std.Address) string\n}\n" + }, + { + "name": "voter.gno", + "body": "package govdao\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tyay = \"YES\"\n\tnay = \"NO\"\n\n\tmsgNoMoreVotesAllowed = \"no more votes allowed\"\n\tmsgAlreadyVoted = \"caller already voted\"\n\tmsgWrongVotingValue = \"voting values must be YES or NO\"\n)\n\nfunc NewPercentageVoter(percent int) *PercentageVoter {\n\tif percent \u003c 0 || percent \u003e 100 {\n\t\tpanic(\"percent value must be between 0 and 100\")\n\t}\n\n\treturn \u0026PercentageVoter{\n\t\tpercentage: percent,\n\t}\n}\n\n// PercentageVoter is a system based on the amount of received votes.\n// When the specified treshold is reached, the voting process finishes.\ntype PercentageVoter struct {\n\tpercentage int\n\n\tvoters []std.Address\n\tyes int\n\tno int\n}\n\nfunc (pv *PercentageVoter) IsAccepted(voters []std.Address) bool {\n\tif len(voters) == 0 {\n\t\treturn true // special case\n\t}\n\n\treturn pv.percent(voters) \u003e= pv.percentage\n}\n\nfunc (pv *PercentageVoter) IsFinished(voters []std.Address) bool {\n\treturn pv.yes+pv.no \u003e= len(voters)\n}\n\nfunc (pv *PercentageVoter) Status(voters []std.Address) string {\n\treturn ufmt.Sprintf(\"YES: %d, NO: %d, percent: %d, members: %d\", pv.yes, pv.no, pv.percent(voters), len(voters))\n}\n\nfunc (pv *PercentageVoter) Vote(voters []std.Address, caller std.Address, flag string) {\n\tif pv.IsFinished(voters) {\n\t\tpanic(msgNoMoreVotesAllowed)\n\t}\n\n\tif pv.alreadyVoted(caller) {\n\t\tpanic(msgAlreadyVoted)\n\t}\n\n\tswitch flag {\n\tcase yay:\n\t\tpv.yes++\n\t\tpv.voters = append(pv.voters, caller)\n\tcase nay:\n\t\tpv.no++\n\t\tpv.voters = append(pv.voters, caller)\n\tdefault:\n\t\tpanic(msgWrongVotingValue)\n\t}\n}\n\nfunc (pv *PercentageVoter) percent(voters []std.Address) int {\n\tif len(voters) == 0 {\n\t\treturn 0\n\t}\n\n\treturn int((float32(pv.yes) / float32(len(voters))) * 100)\n}\n\nfunc (pv *PercentageVoter) alreadyVoted(addr std.Address) bool {\n\tfor _, v := range pv.voters {\n\t\tif v == addr {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "validators", + "path": "gno.land/r/sys/validators", + "files": [ + { + "name": "doc.gno", + "body": "// Package validators implements the on-chain validator set management through Proof of Contribution.\n// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes.\npackage validators\n" + }, + { + "name": "gnosdk.gno", + "body": "package validators\n\nimport (\n\t\"gno.land/p/sys/validators\"\n)\n\n// GetChanges returns the validator changes stored on the realm, since the given block number.\n// This function is intended to be called by gno.land through the GnoSDK\nfunc GetChanges(from int64) []validators.Validator {\n\tvalsetChanges := make([]validators.Validator, 0)\n\n\t// Gather the changes from the specified block\n\tchanges.Iterate(getBlockID(from), \"\", func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\tvalsetChanges = append(valsetChanges, ch.validator)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn valsetChanges\n}\n" + }, + { + "name": "init.gno", + "body": "package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/nt/poa\"\n\t\"gno.land/p/sys/validators\"\n)\n\nfunc init() {\n\t// Prepare the initial validator set\n\tset := []validators.Validator{\n\t\t// core-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g19v2h4pn6lrf8pwvhn8h0anek0cpt2tmhye4vkv\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqm6x7rlcrp96dz60tn4ws5a6mt34aptmj4qrzxv59fcnlrrd59q52vvvrj\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g13us7swtc9hq550y9v4z6vcarak9vf8nqdvcqj4\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp6z4cv32j5sjte5keucvfj6f44m9ctmaj4seqgg4vmekd4jdpzd6hzf0n7\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1ectm6algkfw3qnjmjvx7hacmh358t36ggj5lqv\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpq4pfx2yfnaqy4pf6xwz2mjx8fvw8d3rmfxxdega46g0z3ak2t47wj27kf\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1tcxls3ylnrwrq95j33xpyuct4l370ra7jca4kj\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp8lg7lfxj2lp68txvkh2mrpjkmgatpcgpsmlw53vssx9m2zgtfmnz5teuj\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1mxguhd5zacar64txhfm0v7hhtph5wur5hx86vs\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqz6fwulsygvu9xypka3zqxhkxllm467e3adphmj6y44vn3yy8qq34vxnse\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1t9ctfa468hn6czff8kazw08crazehcxaqa2uaa\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpsq650w975vqsf6ajj5x4wdzfnrh64kmw7sljqz7wts6k0p6l36d0huls3\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1gav33elude7prcdctpjekze7ft9l8qdjxqaj6d\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp7pjfz35u8pm3jld0rsyw4ctphzql7n9jr59vrylq5l4f8qh35nw3pagcx\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t}\n\n\t// The default valset protocol is PoA\n\tvp = poa.NewPoA(poa.WithInitialSet(set))\n\n\t// No changes to apply initially\n\tchanges = avl.NewTree()\n}\n" + }, + { + "name": "poc.gno", + "body": "package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/gov/proposal\"\n\t\"gno.land/p/sys/validators\"\n)\n\nconst daoPkgPath = \"gno.land/r/gov/dao\"\n\nconst (\n\terrNoChangesProposed = \"no set changes proposed\"\n\terrNotGovDAO = \"caller not govdao executor\"\n)\n\n// NewPropExecutor creates a new executor that wraps a changes closure\n// proposal. This wrapper is required to ensure the GovDAO Realm actually\n// executed the callback.\n//\n// Concept adapted from:\n// https://github.com/gnolang/gno/pull/1945\nfunc NewPropExecutor(changesFn func() []validators.Validator) proposal.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\t// Make sure the GovDAO executor runs the valset changes\n\t\tassertGovDAOCaller()\n\n\t\tfor _, change := range changesFn() {\n\t\t\tif change.VotingPower == 0 {\n\t\t\t\t// This change request is to remove the validator\n\t\t\t\tremoveValidator(change.Address)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// This change request is to add the validator\n\t\t\taddValidator(change)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn proposal.NewExecutor(callback)\n}\n\n// assertGovDAOCaller verifies the caller is the GovDAO executor\nfunc assertGovDAOCaller() {\n\tif std.PrevRealm().PkgPath() != daoPkgPath {\n\t\tpanic(errNotGovDAO)\n\t}\n}\n\n// IsValidator returns a flag indicating if the given bech32 address\n// is part of the validator set\nfunc IsValidator(addr std.Address) bool {\n\treturn vp.IsValidator(addr)\n}\n\n// GetValidators returns the typed validator set\nfunc GetValidators() []validators.Validator {\n\treturn vp.GetValidators()\n}\n" + }, + { + "name": "validators.gno", + "body": "package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar (\n\tvp validators.ValsetProtocol // p is the underlying validator set protocol\n\tchanges *avl.Tree // changes holds any valset changes; seqid(block number) -\u003e []change\n)\n\n// change represents a single valset change, tied to a specific block number\ntype change struct {\n\tblockNum int64 // the block number associated with the valset change\n\tvalidator validators.Validator // the validator update\n}\n\n// addValidator adds a new validator to the validator set.\n// If the validator is already present, the method errors out\nfunc addValidator(validator validators.Validator) {\n\tval, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator added, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: val,\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorAddedEvent)\n}\n\n// removeValidator removes the given validator from the set.\n// If the validator is not present in the set, the method errors out\nfunc removeValidator(address std.Address) {\n\tval, err := vp.RemoveValidator(address)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator removed, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: validators.Validator{\n\t\t\tAddress: val.Address,\n\t\t\tPubKey: val.PubKey,\n\t\t\tVotingPower: 0, // nullified the voting power indicates removal\n\t\t},\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorRemovedEvent)\n}\n\n// saveChange saves the valset change\nfunc saveChange(ch change) {\n\tid := getBlockID(ch.blockNum)\n\n\tsetRaw, exists := changes.Get(id)\n\tif !exists {\n\t\tchanges.Set(id, []change{ch})\n\n\t\treturn\n\t}\n\n\t// Save the change\n\tset := setRaw.([]change)\n\tset = append(set, ch)\n\n\tchanges.Set(id, set)\n}\n\n// getBlockID converts the block number to a sequential ID\nfunc getBlockID(blockNum int64) string {\n\treturn seqid.ID(uint64(blockNum)).String()\n}\n\nfunc Render(_ string) string {\n\tvar (\n\t\tsize = changes.Size()\n\t\tmaxDisplay = 10\n\t)\n\n\tif size == 0 {\n\t\treturn \"No valset changes to apply.\"\n\t}\n\n\toutput := \"Valset changes:\\n\"\n\tchanges.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- #%d: %s (%d)\\n\",\n\t\t\t\tch.blockNum,\n\t\t\t\tch.validator.Address.String(),\n\t\t\t\tch.validator.VotingPower,\n\t\t\t)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n" + }, + { + "name": "validators_test.gno", + "body": "package validators\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/sys/validators\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 10,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestValidators_AddRemove(t *testing.T) {\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\tvar (\n\t\tvals = generateTestValidators(100)\n\t\tinitialHeight = int64(123)\n\t)\n\n\t// Add in the validators\n\tfor _, val := range vals {\n\t\taddValidator(val)\n\n\t\t// Make sure the validator is added\n\t\tuassert.True(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialHeight; i \u003c initialHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, val.VotingPower, ch.VotingPower)\n\t\t}\n\t}\n\n\t// Save the beginning height for the removal\n\tinitialRemoveHeight := std.GetHeight()\n\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\t// Remove the validators\n\tfor _, val := range vals {\n\t\tremoveValidator(val.Address)\n\n\t\t// Make sure the validator is removed\n\t\tuassert.False(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialRemoveHeight; i \u003c initialRemoveHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialRemoveHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, uint64(0), ch.VotingPower)\n\t\t}\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "valopers", + "path": "gno.land/r/gnoland/valopers", + "files": [ + { + "name": "init.gno", + "body": "package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n" + }, + { + "name": "valopers.gno", + "body": "// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao\"\n\t\"gno.land/r/sys/validators\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name 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}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s\\n\", v.Name)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.GetOrigCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal comment\n\tcomment := ufmt.Sprintf(\n\t\t\"Proposal to add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\t// Create the govdao proposal\n\tgovdao.Propose(comment, executor)\n}\n" + }, + { + "name": "valopers_test.gno", + "body": "package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\n\t\t})\n\t})\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "config", + "path": "gno.land/r/manfred/config", + "files": [ + { + "name": "config.gno", + "body": "package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.GetOrigCaller() != addr {\n\t\tpanic(\"restricted area\")\n\t}\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "home", + "path": "gno.land/r/manfred/home", + "files": [ + { + "name": "home.gno", + "body": "package home\n\nimport \"gno.land/r/manfred/config\"\n\nvar (\n\ttodos []string\n\tstatus string\n\tmemeImgURL string\n)\n\nfunc init() {\n\ttodos = append(todos, \"fill this todo list...\")\n\tstatus = \"Online\" // Initial status set to \"Online\"\n\tmemeImgURL = \"https://i.imgflip.com/7ze8dc.jpg\"\n}\n\nfunc Render(path string) string {\n\tcontent := \"# Manfred's (gn)home Dashboard\\n\\n\"\n\n\tcontent += \"## Meme\\n\"\n\tcontent += \"![](\" + memeImgURL + \")\\n\\n\"\n\n\tcontent += \"## Status\\n\"\n\tcontent += status + \"\\n\\n\"\n\n\tcontent += \"## Personal ToDo List\\n\"\n\tfor _, todo := range todos {\n\t\tcontent += \"- [ ] \" + todo + \"\\n\"\n\t}\n\tcontent += \"\\n\"\n\n\t// TODO: Implement a feature to list replies on r/boards on my posts\n\t// TODO: Maybe integrate a calendar feature for upcoming events?\n\n\treturn content\n}\n\nfunc AddNewTodo(todo string) {\n\tconfig.AssertIsAdmin()\n\ttodos = append(todos, todo)\n}\n\nfunc DeleteTodo(todoIndex int) {\n\tconfig.AssertIsAdmin()\n\tif todoIndex \u003e= 0 \u0026\u0026 todoIndex \u003c len(todos) {\n\t\t// Remove the todo from the list by merging slices from before and after the todo\n\t\ttodos = append(todos[:todoIndex], todos[todoIndex+1:]...)\n\t} else {\n\t\tpanic(\"Invalid todo index\")\n\t}\n}\n\nfunc UpdateStatus(newStatus string) {\n\tconfig.AssertIsAdmin()\n\tstatus = newStatus\n}\n" + }, + { + "name": "z1_filetest.gno", + "body": "package main\n\nimport \"gno.land/r/manfred/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Online\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n" + }, + { + "name": "z2_filetest.gno", + "body": "package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/manfred/home\"\n)\n\nfunc main() {\n\tstd.TestSetOrigCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AddNewTodo(\"aaa\")\n\thome.AddNewTodo(\"bbb\")\n\thome.AddNewTodo(\"ccc\")\n\thome.AddNewTodo(\"ddd\")\n\thome.AddNewTodo(\"eee\")\n\thome.UpdateStatus(\"Lorem Ipsum\")\n\thome.DeleteTodo(3)\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Lorem Ipsum\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n// - [ ] aaa\n// - [ ] bbb\n// - [ ] ddd\n// - [ ] eee\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "present", + "path": "gno.land/r/manfred/present", + "files": [ + { + "name": "admin.gno", + "body": "package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.GetOrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n" + }, + { + "name": "present_miami23.gno", + "body": "package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n" + }, + { + "name": "present_miami23_filetest.gno", + "body": "package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n" + }, + { + "name": "presentations.gno", + "body": "package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "rewards", + "path": "gno.land/r/sys/rewards", + "files": [ + { + "name": "rewards.gno", + "body": "// This package will be used to manage proof-of-contributions on the exposed smart-contract side.\npackage rewards\n\n// TODO: write specs.\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + }, + { + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "users", + "path": "gno.land/r/sys/users", + "files": [ + { + "name": "verify.gno", + "body": "package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\ntype VerifyNameFunc func(enabled bool, address std.Address, name string) bool\n\nvar (\n\towner = ownable.NewWithAddress(admin) // Package owner\n\tcheckFunc = VerifyNameByUser // Checking namespace callback\n\tenabled = true // For now this package is disabled by default\n)\n\nfunc IsEnabled() bool { return enabled }\n\n// This method ensures that the given address has ownership of the given name.\nfunc IsAuthorizedAddressForName(address std.Address, name string) bool {\n\treturn checkFunc(enabled, address, name)\n}\n\n// VerifyNameByUser checks from the `users` package that the user has correctly\n// registered the given name.\n// This function considers as valid an `address` that matches the `name`.\nfunc VerifyNameByUser(enable bool, address std.Address, name string) bool {\n\tif !enable {\n\t\treturn true\n\t}\n\n\t// Allow user with their own address as name\n\tif address.String() == name {\n\t\treturn true\n\t}\n\n\tif user := users.GetUserByName(name); user != nil {\n\t\treturn user.Address == address\n\t}\n\n\treturn false\n}\n\n// Admin calls\n\n// Enable this package.\nfunc AdminEnable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = true\n}\n\n// Disable this package.\nfunc AdminDisable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = false\n}\n\n// AdminUpdateVerifyCall updates the method that verifies the namespace.\nfunc AdminUpdateVerifyCall(check VerifyNameFunc) {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tcheckFunc = check\n}\n\n// AdminTransferOwnership transfers the ownership to a new owner.\nfunc AdminTransferOwnership(newOwner std.Address) error {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner.TransferOwnership(newOwner)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "50000", + "gas_fee": "1000000ugnot" + }, + "signatures": [ + { + "pub_key": null, + "signature": null + } + ], + "memo": "" + } + ] + } +} \ No newline at end of file diff --git a/misc/deployments/test4.gno.land/genesis_balances.txt b/misc/deployments/test4.gno.land/genesis_balances.txt new file mode 100644 index 00000000000..d9493485c96 --- /dev/null +++ b/misc/deployments/test4.gno.land/genesis_balances.txt @@ -0,0 +1,70 @@ +# Predeploy Accounts + +g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=103000000ugnot # Test1 (just enough for predeployment) + +# Faucet Accounts (Core) + +g1njagaeg7e398hze39ygfgvc4gwsh6lkz7dwnuz=9000000000000000000ugnot # Faucet #0 +g1a5kdc6ykfykq9p6088sclnr7e63hj8y4nnr5gn=9000000000000000000ugnot # Faucet #1 +g1gadp0yq5djelxwv7h8g7wpdf7x5w3vuzwmqrne=9000000000000000000ugnot # Faucet #2 +g1s6ujq0r200a0tfqgnjfflz7ddetsnrwvzxxx74=9000000000000000000ugnot # Faucet #3 +g1d7set9rsdw7ggt9elvzerep4jz80a4hu4crmzg=9000000000000000000ugnot # Faucet #4 +g1cm7u2fqd7drwfcqtn7zsgrfcyvlkkukhhtrxxj=9000000000000000000ugnot # Faucet #5 +g13csus64lj8twrn266837l9e77dusk2zwa794qz=9000000000000000000ugnot # Faucet #6 +g1knpjkw7d5ft2830n8rnatllzq9gu85dmn20nrp=9000000000000000000ugnot # Faucet #7 +g1563d3cw0gdv68le6azw2s63xm0jx9xvgpmfatq=9000000000000000000ugnot # Faucet #8 +g1xja7p9s3ly3tvkgks9x0n6f6yau2hnzl4x8x3d=9000000000000000000ugnot # Faucet #9 + +# Faucet Accounts (DevX) + +g1q6jrp203fq0239pv38sdq3y3urvd6vt5azacpv=9000000000000000000ugnot # Faucet #10 +g13d7jc32adhc39erm5me38w5v7ej7lpvlnqjk73=9000000000000000000ugnot # Faucet #11 + +# Core Team + +g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=9000000000000000000ugnot # Jae +g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq=9000000000000000000ugnot # Manfred +g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2=9000000000000000000ugnot # Milos +g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7=9000000000000000000ugnot # Nemanja +g17p2kkyy9lp2z3ecw4psssk357vxp20afnyl00d=9000000000000000000ugnot # Petar +g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd=9000000000000000000ugnot # Marc +g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl=9000000000000000000ugnot # Antonio +g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x=9000000000000000000ugnot # Guilhem +g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j=9000000000000000000ugnot # Ray +g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq=9000000000000000000ugnot # Maxwell +g1acn3xssksatydd0fcuslvgmjyw0fzkjdhusddg=9000000000000000000ugnot # Dylan +g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864=9000000000000000000ugnot # Morgan +g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr=9000000000000000000ugnot # Sergio + +# DevRel + +g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5=9000000000000000000ugnot # Michelle +g125em6arxsnj49vx35f0n0z34putv5ty3376fg5=9000000000000000000ugnot # Leon + +# DevX Team + +g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu=9000000000000000000ugnot # Ilker +g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun=9000000000000000000ugnot # Jerónimo +g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd=9000000000000000000ugnot # Denis +g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7=9000000000000000000ugnot # Danny +g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh=9000000000000000000ugnot # Salvo +g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq=9000000000000000000ugnot # Alexis + +# Onbloc + +g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5=9000000000000000000ugnot # GnoSwap +g18l9us6trqaljw39j94wzf5ftxmd9qqkvrxghd2=9000000000000000000ugnot # Adena + +# Berty + +g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm=9000000000000000000ugnot # Berty Core Team + +# Dragos + +g16f5chytu99dmjqtekxf8qzg04vcv7dck6qny6d=9000000000000000000ugnot # Flippando faucet +g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3=9000000000000000000ugnot # ZenTasktic faucet + +# Teritori + +g16n55jt73sl8s5kl3z5ahrf0qrlxp47n205j9ex=9000000000000000000ugnot # Teritori Core Team +g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a=9000000000000000000ugnot # Norman diff --git a/misc/devdeps/Makefile b/misc/devdeps/Makefile index 54df62cc031..6b414555c97 100644 --- a/misc/devdeps/Makefile +++ b/misc/devdeps/Makefile @@ -1,3 +1,6 @@ install: go install mvdan.cc/gofumpt go install google.golang.org/protobuf/cmd/protoc-gen-go + +tidy: + go mod tidy diff --git a/misc/devdeps/deps.go b/misc/devdeps/deps.go index 7ac068c71ac..a011868e4c2 100644 --- a/misc/devdeps/deps.go +++ b/misc/devdeps/deps.go @@ -26,4 +26,7 @@ import ( // linter _ "github.com/golangci/golangci-lint/cmd/golangci-lint" + + // embedmd + _ "github.com/campoy/embedmd/embedmd" ) diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index 3a1f87c0664..2ca693afc93 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -1,29 +1,36 @@ module github.com/gnolang/gno/misc/devdeps -go 1.21 +go 1.22 + +toolchain go1.22.4 require ( - github.com/golangci/golangci-lint v1.54.2 // sync with github action - golang.org/x/tools v0.12.0 - golang.org/x/tools/gopls v0.12.4 - google.golang.org/protobuf v1.30.0 + github.com/golangci/golangci-lint v1.59.1 // sync with github action + golang.org/x/tools v0.22.1-0.20240628205440-9c895dd76b34 + golang.org/x/tools/gopls v0.16.1 + google.golang.org/protobuf v1.33.0 moul.io/testman v1.5.0 - mvdan.cc/gofumpt v0.5.0 + mvdan.cc/gofumpt v0.6.0 ) +require github.com/campoy/embedmd v1.0.0 + require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/4meepo/tagalign v1.3.2 // indirect - github.com/Abirdcfly/dupword v0.0.12 // indirect - github.com/Antonboom/errname v0.1.12 // indirect - github.com/Antonboom/nilnil v0.1.7 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect + github.com/4meepo/tagalign v1.3.4 // indirect + github.com/Abirdcfly/dupword v0.0.14 // indirect + github.com/Antonboom/errname v0.1.13 // indirect + github.com/Antonboom/nilnil v0.1.9 // indirect + github.com/Antonboom/testifylint v1.3.1 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/Crocmagnon/fatcontext v0.2.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.1.0 // indirect - github.com/alexkohler/nakedret/v2 v2.0.2 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/alecthomas/go-check-sumtype v0.1.4 // indirect + github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect @@ -31,119 +38,120 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v3 v3.4.0 // indirect - github.com/breml/bidichk v0.2.4 // indirect - github.com/breml/errchkjson v0.3.1 // indirect - github.com/butuzov/ireturn v0.2.0 // indirect - github.com/butuzov/mirror v1.1.0 // indirect - github.com/ccojocar/zxcvbn-go v1.0.1 // indirect + github.com/bombsimon/wsl/v4 v4.2.1 // indirect + github.com/breml/bidichk v0.2.7 // indirect + github.com/breml/errchkjson v0.3.6 // 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/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect - github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.1.2 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.11.0 // indirect + github.com/daixiang0/gci v0.13.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denis-tingaikin/go-header v0.4.3 // indirect - github.com/esimonov/ifshort v1.0.4 // indirect - github.com/ettle/strcase v0.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/go-critic/go-critic v0.9.0 // indirect + github.com/ghostiam/protogetter v0.3.6 // indirect + github.com/go-critic/go-critic v0.11.4 // 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.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect 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.0.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect - github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect - github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect - github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect - github.com/golangci/misspell v0.4.1 // indirect - github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect - github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect + github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // 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/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/nilerr v0.1.1 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // 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.5.1 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect + github.com/jjti/go-spancheck v0.6.1 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/kisielk/errcheck v1.6.3 // indirect - github.com/kisielk/gotool v1.0.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.4 // indirect + github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect + github.com/kisielk/errcheck v1.7.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.8 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/ldez/gomoddirectives v0.2.3 // indirect + github.com/lasiar/canonicalheader v1.1.1 // indirect + github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect - github.com/leonklingele/grouper v1.1.1 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect github.com/lufeee/execinquery v1.2.1 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // 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/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.3.2 // indirect + github.com/mgechev/revive v1.3.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.1 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.11.0 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.13.5 // indirect + github.com/nunnatsa/ginkgolinter v0.16.2 // 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.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // 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.4.4 // indirect + github.com/polyfloyd/go-errorlint v1.5.2 // 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/quasilyte/go-ruleguard v0.4.0 // indirect + github.com/quasilyte/go-ruleguard v0.4.2 // 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/ryancurrah/gomodguard v1.3.0 // indirect - github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect + github.com/ryancurrah/gomodguard v1.3.2 // 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/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect - github.com/securego/gosec/v2 v2.17.0 // indirect - github.com/sergi/go-diff v1.2.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.26.0 // indirect + github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/sivchari/tenv v1.7.1 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -151,44 +159,45 @@ require ( github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.14 // indirect + github.com/tetafro/godot v1.4.16 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect - github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.8.3 // 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.0.5 // indirect - github.com/uudashr/gocognit v1.0.7 // indirect - github.com/xen0n/gosmopolitan v1.2.1 // indirect + github.com/ultraware/whitespace v0.1.1 // indirect + github.com/uudashr/gocognit v1.1.2 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect - github.com/ykadowak/zerologlint v0.1.3 // 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 - gitlab.com/bosi/decorder v0.4.0 // indirect - go.tmz.dev/musttag v0.7.2 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.12.2 // indirect + go-simpler.org/sloglint v0.7.1 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect - golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/telemetry v0.0.0-20240607193123-221703e18637 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/vuln v1.0.4 // 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.4.5 // indirect + honnef.co/go/tools v0.4.7 // indirect moul.io/banner v1.0.1 // indirect moul.io/motd v1.0.0 // indirect moul.io/u v1.27.0 // indirect - mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect - mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect - mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect - mvdan.cc/xurls/v2 v2.4.0 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect + mvdan.cc/xurls/v2 v2.5.0 // indirect ) diff --git a/misc/devdeps/go.sum b/misc/devdeps/go.sum index 9146980fe7d..4c3f84b6df7 100644 --- a/misc/devdeps/go.sum +++ b/misc/devdeps/go.sum @@ -7,7 +7,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT 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.44.3/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= @@ -18,9 +17,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV 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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 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= @@ -38,35 +34,44 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo 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= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.2 h1:1idD3yxlRGV18VjqtDbqYvQ5pXqQS0wO2dn6M3XstvI= -github.com/4meepo/tagalign v1.3.2/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/Abirdcfly/dupword v0.0.12 h1:56NnOyrXzChj07BDFjeRA+IUzSz01jmzEq+G4kEgFhc= -github.com/Abirdcfly/dupword v0.0.12/go.mod h1:+us/TGct/nI9Ndcbcp3rgNcQzctTj68pq7TcgNpLfdI= -github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= -github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= -github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= -github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= +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= +github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= +github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= +github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= +github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= +github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= +github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= +github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4= +github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/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.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= +github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= 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.1.0 h1:3ZBs7LAezy8gh0uECsA6CGU43FF3zsx5f4eah5FxTMA= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0/go.mod h1:rZLTje5A9kFBe0pzhpe2TdhRniBF++PRHQuRpR8esVc= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= -github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +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.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= +github.com/alecthomas/go-check-sumtype v0.1.4/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/alexkohler/nakedret/v2 v2.0.2 h1:qnXuZNvv3/AxkAb22q/sEsEpcA99YxLFACDtEw9TPxE= -github.com/alexkohler/nakedret/v2 v2.0.2/go.mod h1:2b8Gkk0GsOrqQv/gPWjNLDSKwG8I5moSXG1K4VIBcTQ= +github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= +github.com/alexkohler/nakedret/v2 v2.0.4/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= @@ -83,69 +88,70 @@ github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJ github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= -github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= -github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= -github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= -github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= -github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= -github.com/butuzov/ireturn v0.2.0 h1:kCHi+YzC150GE98WFuZQu9yrTn6GEydO2AuPLbTgnO4= -github.com/butuzov/ireturn v0.2.0/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= -github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= -github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= -github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4= -github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= +github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= +github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= +github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= +github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= +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/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/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/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.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= +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/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= +github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= 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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/daixiang0/gci v0.11.0 h1:XeQbFKkCRxvVyn06EOuNY6LPGBLVuB/W130c8FrnX6A= -github.com/daixiang0/gci v0.11.0/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= +github.com/daixiang0/gci v0.13.4/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/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= -github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +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.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 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.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +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.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/go-critic/go-critic v0.9.0 h1:Pmys9qvU3pSML/3GEQ2Xd9RZ/ip+aXHKILuxczKGV/U= -github.com/go-critic/go-critic v0.9.0/go.mod h1:5P8tdXL7m/6qnyG6oRAlYLORvoXH0WDypYgAEmagT40= +github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= +github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= +github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= 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= @@ -155,18 +161,19 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb 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/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= @@ -178,6 +185,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -212,28 +221,25 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +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-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= -github.com/golangci/golangci-lint v1.54.2 h1:oR9zxfWYxt7hFqk6+fw6Enr+E7F0SN2nqHhJYyIb0yo= -github.com/golangci/golangci-lint v1.54.2/go.mod h1:vnsaCTPKCI2wreL9tv7RkHDwUrz3htLjed6+6UsvcwU= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= -github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= +github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks= +github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg= +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/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= @@ -248,12 +254,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/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/martian/v3 v3.1.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= @@ -261,18 +266,15 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf 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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8= +github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= 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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +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= @@ -285,13 +287,9 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW 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/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -299,15 +297,18 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T 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/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/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.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= -github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jba/templatecheck v0.7.0 h1:wjTb/VhGgSFeim5zjWVePBdaMo28X74bGLSABZV+zIA= +github.com/jba/templatecheck v0.7.0/go.mod h1:n1Etw+Rrw1mDDD8dDRsEKTwMZsJ98EkktgNJC6wLUGo= +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/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= +github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= 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= @@ -319,16 +320,16 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V 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/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= -github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= +github.com/kisielk/errcheck v1.7.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.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= -github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= +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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -340,18 +341,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.8 h1:Ul2KsqtzFxTlSU7IP0JusWlLiNqQaloB9vguyjbE558= -github.com/kunwardeep/paralleltest v1.0.8/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +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/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= +github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= +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/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +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/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= @@ -365,16 +370,14 @@ github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwM 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-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 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/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/revive v1.3.2 h1:Wb8NQKBaALBJ3xrrj4zpwJwqwNA6nDpyJSEQWcCka6U= -github.com/mgechev/revive v1.3.2/go.mod h1:UCLtc7o5vg5aXCwdUTU1kEBQ1v+YXPAkYDIDXbrs5I0= +github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= +github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= 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= @@ -390,20 +393,21 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= -github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= +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.13.5 h1:fOsPB4CEZOPkyMqF4B9hoqOpooFWU7vWSVkCSscVpgU= -github.com/nunnatsa/ginkgolinter v0.13.5/go.mod h1:OBHy4536xtuX3102NM63XRtOyxqZOO02chsaeDWXVO8= +github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= +github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= 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.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= +github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= +github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 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= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= @@ -411,8 +415,8 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH 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.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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= @@ -421,11 +425,12 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK 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/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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.4.4 h1:A9gytp+p6TYqeALTYRoxJESYP8wJRETRX2xzGWFsEBU= -github.com/polyfloyd/go-errorlint v1.4.4/go.mod h1:ry5NqF7l9Q77V+XqAfUg1zfryrEtyac3G5+WVpIK0xU= +github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA= +github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= +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= @@ -448,8 +453,10 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O 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/quasilyte/go-ruleguard v0.4.0 h1:DyM6r+TKL+xbKB4Nm7Afd1IQh9kEUKQs2pboWGKtvQo= -github.com/quasilyte/go-ruleguard v0.4.0/go.mod h1:Eu76Z/R8IXtViWUIHkE3p8gdH3/PKk1eh3YGfaEof10= +github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= +github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -457,25 +464,24 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:r 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= -github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= -github.com/ryanrolds/sqlclosecheck v0.4.0 h1:i8SX60Rppc1wRuyQjMciLqIzV3xnoHB7/tXbr6RGYNI= -github.com/ryanrolds/sqlclosecheck v0.4.0/go.mod h1:TBRRjzL31JONc9i4XMinicuo+s+E8yKZ5FN8X3G6CKQ= +github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= +github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= +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/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.24.0 h1:MKNzmXtGh5N0y74Z/CIaJh4GlB364l0K1RUT08WSWAc= -github.com/sashamelentyev/usestdlibvars v1.24.0/go.mod h1:9cYkq+gYJ+a5W2RPdhfaSCnTVUC1OQP/bSiiBhq3OZE= -github.com/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI= -github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc= +github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE= +github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= +github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 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= @@ -487,16 +493,14 @@ 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= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= 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.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +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.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -514,8 +518,9 @@ github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8L 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 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= @@ -524,9 +529,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/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/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= @@ -538,30 +543,30 @@ 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.14 h1:ScO641OHpf9UpHPk8fCknSuXNMpi4iFlwuWoBs3L+1s= -github.com/tetafro/godot v1.4.14/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= +github.com/tetafro/godot v1.4.16/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/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= -github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= +github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= +github.com/tomarrell/wrapcheck/v2 v2.8.3/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.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= -github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo= -github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= -github.com/xen0n/gosmopolitan v1.2.1 h1:3pttnTuFumELBRSh+KQs1zcz4fN6Zy7aB0xlnQSn1Iw= -github.com/xen0n/gosmopolitan v1.2.1/go.mod h1:JsHq/Brs1o050OOdmzHeOr0N7OtlnKRAGAsElF8xBQA= +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.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= +github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= +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= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -github.com/ykadowak/zerologlint v0.1.3 h1:TLy1dTW3Nuc+YE3bYRPToG1Q9Ej78b5UUN6bjbGdxPE= -github.com/ykadowak/zerologlint v0.1.3/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +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= @@ -570,19 +575,22 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 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= -gitlab.com/bosi/decorder v0.4.0 h1:HWuxAhSxIvsITcXeP+iIRg9d1cVfvVkmlF7M68GaoDY= -gitlab.com/bosi/decorder v0.4.0/go.mod h1:xarnteyUoJiOTEldDysquWKTVDCKo2TOIOIibSuWqOg= -go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E= -go-simpler.org/assert v0.6.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +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.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= +go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= +go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU= +go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c= 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.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.tmz.dev/musttag v0.7.2 h1:1J6S9ipDbalBSODNT5jCep8dhZyMr4ttnjQagmGYR5s= -go.tmz.dev/musttag v0.7.2/go.mod h1:m6q5NiiSKMnQYokefa2xGoyoXnrswCbJ0AWYzf4Zs28= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= 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= @@ -597,9 +605,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U 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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/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= @@ -611,12 +617,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= -golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ= -golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/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= @@ -629,7 +635,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl 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/lint v0.0.0-20201208152925-83fdc39ff7b5/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= @@ -647,8 +652,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 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.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -678,9 +683,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R 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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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= @@ -690,17 +692,13 @@ 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.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= @@ -715,8 +713,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +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.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= @@ -748,17 +746,12 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210423185535-09eb48e85fd7/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= @@ -775,8 +768,11 @@ 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.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240607193123-221703e18637 h1:3Wt8mZlbFwG8llny+t18kh7AXxyWePFycXMuVdHxnyM= +golang.org/x/telemetry v0.0.0-20240607193123-221703e18637/go.mod h1:n38mvGdgc4dA684EC4NwQwoPKSw4jyKw8/DgZHDA1Dk= 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= @@ -788,14 +784,13 @@ 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.4/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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/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= @@ -847,15 +842,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc 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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -869,12 +857,12 @@ 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.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= -golang.org/x/tools/gopls v0.12.4 h1:nce5etAamR46d9oNGxop1aRK5rDQ0NqcY/SHIcyfEKY= -golang.org/x/tools/gopls v0.12.4/go.mod h1:TVMDG6cF53o2lhDFCp4nd0XfCp9MWdaYp3d2QxfZ9JI= -golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815 h1:A9kONVi4+AnuOr1dopsibH6hLi1Huy54cbeJxnq4vmU= -golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815/go.mod h1:XJiVExZgoZfrrxoTeVsFYrSSk1snhfpOEC95JL+A4T0= +golang.org/x/tools v0.22.1-0.20240628205440-9c895dd76b34 h1:Kd+Z5Pm6uwYx3T2KEkeHMHUMZxDPb/q6b1m+zEcy62c= +golang.org/x/tools v0.22.1-0.20240628205440-9c895dd76b34/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools/gopls v0.16.1 h1:1hO/dCeUvjEYx3V0rVvCtOkwnpEpqS29paE+Jw4dcAc= +golang.org/x/tools/gopls v0.16.1/go.mod h1:Mwg8NfkbmP57kHtr/qsiU1+7kyEpuCvlPs7MH6sr988= +golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I= +golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ= 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= @@ -895,16 +883,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M 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/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 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/appengine v1.6.7/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= @@ -934,13 +918,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc 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/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/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= @@ -953,10 +930,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa 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/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 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= @@ -969,8 +942,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -999,8 +972,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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.4.5 h1:YGD4H+SuIOOqsyoLOpZDWcieM28W47/zRO7f+9V3nvo= -honnef.co/go/tools v0.4.5/go.mod h1:GUV+uIBCLpdf0/v6UhHHG/yzI/z6qPskBeQCjcNB96k= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= 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= @@ -1011,16 +984,12 @@ moul.io/testman v1.5.0/go.mod h1:b4/5+lMsMDJtwuh25Cr0eVJ5Y4B2lSPfkzDtfct070g= moul.io/u v1.6.0/go.mod h1:yd3/IoYRIJaZWAJV2rYHvM2EPp/Pp0zSNraB5IPX+hw= moul.io/u v1.27.0 h1:rF0p184mludn2DzL0unA8Gf/mFWMBerdqOh8cyuQYzQ= moul.io/u v1.27.0/go.mod h1:ggYDXxUjoHpfDsMPD3STqkUZTyA741PZiQhSd+7kRnA= -mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= -mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= -mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc= -mvdan.cc/xurls/v2 v2.4.0/go.mod h1:+GEjq9uNjqs8LQfM9nVnM8rff0OQ5Iash5rzX+N1CSg= +mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= 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= diff --git a/misc/docs-linter/errors.go b/misc/docs-linter/errors.go new file mode 100644 index 00000000000..c116df3fe16 --- /dev/null +++ b/misc/docs-linter/errors.go @@ -0,0 +1,12 @@ +package main + +import "errors" + +var ( + errEmptyPath = errors.New("you need to pass in a path to scan") + err404Link = errors.New("link returned a 404") + errFound404Links = errors.New("found links resulting in a 404 response status") + errFoundUnescapedJSXTags = errors.New("found unescaped JSX tags") + errFoundUnreachableLocalLinks = errors.New("found local links that stat fails on") + errFoundLintItems = errors.New("found items that need linting") +) diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod new file mode 100644 index 00000000000..be771c9a952 --- /dev/null +++ b/misc/docs-linter/go.mod @@ -0,0 +1,21 @@ +module linter + +go 1.22 + +toolchain go1.22.4 + +require ( + github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c + github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.7.0 + mvdan.cc/xurls/v2 v2.5.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/misc/docs-linter/go.sum b/misc/docs-linter/go.sum new file mode 100644 index 00000000000..ab8c3cf7c48 --- /dev/null +++ b/misc/docs-linter/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c h1:jtZ+oN8ZpBM0wYbcFH0B7NjFFzTFqZZmZellSSKtaCE= +github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c/go.mod h1:YcZbtNIfXVn4jS1pSG8SeG5RVHjyI7FPS3GypZaXxCI= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= diff --git a/misc/docs-linter/jsx.go b/misc/docs-linter/jsx.go new file mode 100644 index 00000000000..d0307680a0c --- /dev/null +++ b/misc/docs-linter/jsx.go @@ -0,0 +1,62 @@ +package main + +import ( + "bytes" + "fmt" + "regexp" + "strings" +) + +var ( + reCodeBlocks = regexp.MustCompile("(?s)```.*?```") + reInlineCode = regexp.MustCompile("`[^`]*`") +) + +// extractJSX extracts JSX tags from given file content +func extractJSX(fileContent []byte) []string { + text := string(fileContent) + + // Remove code blocks + contentNoCodeBlocks := reCodeBlocks.ReplaceAllString(text, "") + + // Remove inline code + contentNoInlineCode := reInlineCode.ReplaceAllString(contentNoCodeBlocks, "") + + // Extract JSX/HTML elements + reJSX := regexp.MustCompile("(?s)<[^>]+>") + + matches := reJSX.FindAllString(contentNoInlineCode, -1) + + filteredMatches := make([]string, 0) + // Ignore HTML comments and escaped JSX + for _, m := range matches { + if !strings.Contains(m, "!--") && !strings.Contains(m, "\\>") { + filteredMatches = append(filteredMatches, m) + } + } + + return filteredMatches +} + +func lintJSX(filepathToJSX map[string][]string) (string, error) { + var ( + found bool + output bytes.Buffer + ) + for filePath, tags := range filepathToJSX { + for _, tag := range tags { + if !found { + output.WriteString("Tags that need checking:\n") + found = true + } + + output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", tag, filePath)) + } + } + + if found { + return output.String(), errFoundUnescapedJSXTags + } + + return "", nil +} diff --git a/misc/docs-linter/links.go b/misc/docs-linter/links.go new file mode 100644 index 00000000000..744917d8dfb --- /dev/null +++ b/misc/docs-linter/links.go @@ -0,0 +1,93 @@ +package main + +import ( + "bufio" + "bytes" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +// Valid start to an embedmd link +const embedmd = `[embedmd]:# ` + +// Regular expression to match markdown links +var regex = regexp.MustCompile(`]\((\.\.?/.+?)\)`) + +// extractLocalLinks extracts links to local files from the given file content +func extractLocalLinks(fileContent []byte) []string { + scanner := bufio.NewScanner(bytes.NewReader(fileContent)) + links := make([]string, 0) + + // Scan file line by line + for scanner.Scan() { + line := scanner.Text() + + // Check for embedmd links + if embedmdPos := strings.Index(line, embedmd); embedmdPos != -1 { + link := line[embedmdPos+len(embedmd)+1:] + + // Find closing parentheses + if closePar := strings.LastIndex(link, ")"); closePar != -1 { + link = link[:closePar] + } + + // Remove space + if pos := strings.Index(link, " "); pos != -1 { + link = link[:pos] + } + + // Add link to be checked + links = append(links, link) + continue + } + + // Find all matches + matches := regex.FindAllString(line, -1) + + // Extract and print the local file links + for _, match := range matches { + // Remove ]( from the beginning and ) from end of link + match = match[2 : len(match)-1] + + // Remove markdown headers in links + if pos := strings.Index(match, "#"); pos != -1 { + match = match[:pos] + } + + links = append(links, match) + } + } + + return links +} + +func lintLocalLinks(filepathToLinks map[string][]string, docsPath string) (string, error) { + var ( + found bool + output bytes.Buffer + ) + + for filePath, links := range filepathToLinks { + for _, link := range links { + path := filepath.Join(docsPath, 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 + } + + output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", link, filePath)) + } + } + } + + if found { + return output.String(), errFoundUnreachableLocalLinks + } + + return "", nil +} diff --git a/misc/docs-linter/main.go b/misc/docs-linter/main.go new file mode 100644 index 00000000000..97d80316108 --- /dev/null +++ b/misc/docs-linter/main.go @@ -0,0 +1,173 @@ +package main + +import ( + "bytes" + "context" + "flag" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/gnolang/gno/tm2/pkg/commands" + "golang.org/x/sync/errgroup" +) + +type cfg struct { + docsPath string +} + +func main() { + cfg := &cfg{} + + cmd := commands.NewCommand( + commands.Metadata{ + Name: "docs-linter", + ShortUsage: "docs-linter [flags]", + ShortHelp: `Lints the .md files in the given folder & subfolders. +Checks for 404 links (local and remote), as well as improperly escaped JSX tags.`, + }, + cfg, + func(ctx context.Context, args []string) error { + res, err := execLint(cfg, ctx) + if len(res) != 0 { + fmt.Println(res) + } + + return err + }) + + cmd.Execute(context.Background(), os.Args[1:]) +} + +func (c *cfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.docsPath, + "path", + "./", + "path to dir to walk for .md files", + ) +} + +func execLint(cfg *cfg, ctx context.Context) (string, error) { + if cfg.docsPath == "" { + return "", errEmptyPath + } + + absPath, err := filepath.Abs(cfg.docsPath) + if err != nil { + return "", fmt.Errorf("error getting absolute path for docs folder: %w", err) + } + + // Main buffer to write to the end user after linting + var output bytes.Buffer + output.WriteString(fmt.Sprintf("Linting %s...\n", absPath)) + + // Find docs files to lint + mdFiles, err := findFilePaths(cfg.docsPath) + if err != nil { + return "", fmt.Errorf("error finding .md files: %w", err) + } + + // Make storage maps for tokens to analyze + filepathToURLs := make(map[string][]string) // file path > [urls] + filepathToJSX := make(map[string][]string) // file path > [JSX items] + filepathToLocalLink := make(map[string][]string) // file path > [local links] + + // Extract tokens from files + for _, filePath := range mdFiles { + // Read file content once and pass it to linters + fileContents, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + + // Execute JSX extractor + filepathToJSX[filePath] = extractJSX(fileContents) + + // Execute URL extractor + filepathToURLs[filePath] = extractUrls(fileContents) + + // Execute local link extractor + filepathToLocalLink[filePath] = extractLocalLinks(fileContents) + } + + // Run linters in parallel + g, _ := errgroup.WithContext(ctx) + + var writeLock sync.Mutex + + g.Go(func() error { + res, err := lintJSX(filepathToJSX) + if err != nil { + writeLock.Lock() + output.WriteString(res) + writeLock.Unlock() + } + + return err + }) + + g.Go(func() error { + res, err := lintURLs(filepathToURLs, ctx) + if err != nil { + writeLock.Lock() + output.WriteString(res) + writeLock.Unlock() + } + + return err + }) + + g.Go(func() error { + res, err := lintLocalLinks(filepathToLocalLink, cfg.docsPath) + if err != nil { + writeLock.Lock() + output.WriteString(res) + writeLock.Unlock() + } + + return err + }) + + if err = g.Wait(); err != nil { + return output.String(), errFoundLintItems + } + + output.WriteString("Lint complete, no issues found.") + return output.String(), nil +} + +// findFilePaths gathers the file paths for specific file types +func findFilePaths(startPath string) ([]string, error) { + filePaths := make([]string, 0) + + walkFn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("error accessing file: %w", err) + } + + // Check if the file is a dir + if info.IsDir() { + return nil + } + + // Check if the file type matches + if !strings.HasSuffix(info.Name(), ".md") { + return nil + } + + // File is not a directory + filePaths = append(filePaths, path) + + return nil + } + + // Walk the directory root recursively + if walkErr := filepath.Walk(startPath, walkFn); walkErr != nil { + return nil, fmt.Errorf("unable to walk directory, %w", walkErr) + } + + return filePaths, nil +} diff --git a/misc/docs-linter/main_test.go b/misc/docs-linter/main_test.go new file mode 100644 index 00000000000..f42874ea81e --- /dev/null +++ b/misc/docs-linter/main_test.go @@ -0,0 +1,270 @@ +package main + +import ( + "context" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEmptyPathError(t *testing.T) { + t.Parallel() + + cfg := &cfg{ + docsPath: "", + } + + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFn() + + _, err := execLint(cfg, ctx) + assert.ErrorIs(t, err, errEmptyPath) +} + +func TestExtractLinks(t *testing.T) { + t.Parallel() + + // Create mock file content with random links + mockFileContent := `# Lorem Ipsum +Lorem ipsum dolor sit amet, +[consectetur](https://example.org) +adipiscing elit. Vivamus lacinia odio +vitae [vestibulum vestibulum](http://localhost:3000). +Cras [vel ex](http://192.168.1.1) et +turpis egestas luctus. Nullam +[eleifend](https://www.wikipedia.org) +nulla ac [blandit tempus](https://gitlab.org). +## Valid Links Here are some valid links: +- [Mozilla](https://mozilla.org) +- [Valid URL](https://valid-url.net) +- [Another Valid URL](https://another-valid-url.info) +- [Valid Link](https://valid-link.edu) +` + + // Expected URLs + expectedUrls := []string{ + "https://example.org", + "http://192.168.1.1", + "https://www.wikipedia.org", + "https://gitlab.org", + "https://mozilla.org", + "https://valid-url.net", + "https://another-valid-url.info", + "https://valid-link.edu", + } + + // Extract URLs from each file in the sourceDir + extractedUrls := extractUrls([]byte(mockFileContent)) + + if len(expectedUrls) != len(extractedUrls) { + t.Fatal("did not extract correct amount of URLs") + } + + sort.Strings(extractedUrls) + sort.Strings(expectedUrls) + + for i, u := range expectedUrls { + require.Equal(t, u, extractedUrls[i]) + } +} + +func TestExtractJSX(t *testing.T) { + t.Parallel() + + // Create mock file content with random JSX tags + mockFileContent := ` +#### Usage + +### getFunctionSignatures + +Fetches public facing function signatures + +#### Parameters + +Returns **Promise** + +# test text from gnodev.md + +#### Usage +### evaluateExpression + +Evaluates any expression in readonly mode and returns the results + +#### Parameters + +Returns **Promise** +` + + // Expected JSX tags + expectedTags := []string{ + "", + "", + "", + } + + // Extract JSX tags from the mock file content + extractedTags := extractJSX([]byte(mockFileContent)) + + if len(expectedTags) != len(extractedTags) { + t.Fatal("did not extract the correct amount of JSX tags") + } + + sort.Strings(extractedTags) + sort.Strings(expectedTags) + + for i, tag := range expectedTags { + require.Equal(t, tag, extractedTags[i]) + } +} + +func TestExtractLocalLinks(t *testing.T) { + t.Parallel() + + // Create mock file content with random local links + mockFileContent := ` +Here is some text with a link to a local file: [text](../concepts/file1.md) +Here is another local link: [another](./path/to/file1.md) +Here is another local link: [another](./path/to/file2.md#header-1-2) +And a link to an external website: [example](https://example.com) +And a websocket link: [websocket](ws://example.com/socket) +Here's an embedmd link: [embedmd]:# (../assets/how-to-guides/simple-library/tapas.gno go) +Here's an embedmd link: [embedmd]:# (../assets/myfile.sol go) +Here's an embedmd link: [embedmd]:# (../assets/myfi()le.gno c) +Here's an embedmd link: [embedmd]:# (../assets/)myfi(le.gno c) +Here's another link: [embedmd]:# (../folder/myfile.gno c +` + + // Expected local links + expectedLinks := []string{ + "../concepts/file1.md", + "./path/to/file1.md", + "./path/to/file2.md", + "../assets/how-to-guides/simple-library/tapas.gno", + "../assets/myfile.sol", + "../assets/myfi()le.gno", + "../assets/)myfi(le.gno", + "../folder/myfile.gno", + } + + // Extract local links tags from the mock file content + extractedLinks := extractLocalLinks([]byte(mockFileContent)) + + if len(expectedLinks) != len(extractedLinks) { + t.Fatal("did not extract the correct amount of local links") + } + + sort.Strings(extractedLinks) + sort.Strings(expectedLinks) + + for i, tag := range expectedLinks { + require.Equal(t, tag, extractedLinks[i]) + } +} + +func TestFindFilePaths(t *testing.T) { + t.Parallel() + + tempDir, err := os.MkdirTemp(".", "test") + require.NoError(t, err) + t.Cleanup(removeDir(t, tempDir)) + + numSourceFiles := 20 + testFiles := make([]string, numSourceFiles) + + for i := 0; i < numSourceFiles; i++ { + testFiles[i] = "sourceFile" + strconv.Itoa(i) + ".md" + } + + for _, file := range testFiles { + filePath := filepath.Join(tempDir, file) + err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm) + require.NoError(t, err) + + _, err = os.Create(filePath) + require.NoError(t, err) + } + + results, err := findFilePaths(tempDir) + require.NoError(t, err) + + expectedResults := make([]string, 0, len(testFiles)) + + for _, testFile := range testFiles { + expectedResults = append(expectedResults, filepath.Join(tempDir, testFile)) + } + + sort.Slice(results, func(i, j int) bool { + return results[i] < results[j] + }) + + sort.Slice(expectedResults, func(i, j int) bool { + return expectedResults[i] < expectedResults[j] + }) + + require.Equal(t, len(results), len(expectedResults)) + + for i, result := range results { + if result != expectedResults[i] { + require.Equal(t, result, expectedResults[i]) + } + } +} + +func TestFlow(t *testing.T) { + t.Parallel() + + tempDir, err := os.MkdirTemp(".", "test") + require.NoError(t, err) + t.Cleanup(removeDir(t, tempDir)) + + contents := `This is a [broken Wikipedia link](https://www.wikipedia.org/non-existent-page). +Here's an embedmd link that links to a non-existing file: [embedmd]:# (../assets/myfile.sol go) +and here is some JSX tags +and [this is a link to a non-existent](../myfolder/myfile.md) file.` + + expectedItems := []string{ + "https://www.wikipedia.org/non-existent-page", + "../assets/myfile.sol", + "", + "../myfolder/myfile.md", + } + + filePath := filepath.Join(tempDir, "examplefile.md") + + err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm) + require.NoError(t, err) + + f, err := os.Create(filePath) + require.NoError(t, err) + + _, err = f.WriteString(contents) + require.NoError(t, err) + + err = f.Close() + require.NoError(t, err) + + res, err := execLint(&cfg{ + docsPath: tempDir, + }, + context.Background(), + ) + + assert.ErrorIs(t, err, errFoundLintItems) + + for _, item := range expectedItems { + assert.True(t, strings.Contains(res, item)) + } +} + +func removeDir(t *testing.T, dirPath string) func() { + return func() { + require.NoError(t, os.RemoveAll(dirPath)) + } +} diff --git a/misc/docs-linter/urls.go b/misc/docs-linter/urls.go new file mode 100644 index 00000000000..093e624d81e --- /dev/null +++ b/misc/docs-linter/urls.go @@ -0,0 +1,108 @@ +package main + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "net/http" + "strings" + "sync" + + "golang.org/x/sync/errgroup" + "mvdan.cc/xurls/v2" +) + +// extractUrls extracts urls from given file content +func extractUrls(fileContent []byte) []string { + scanner := bufio.NewScanner(bytes.NewReader(fileContent)) + urls := make([]string, 0) + + // Scan file line by line + for scanner.Scan() { + line := scanner.Text() + + // Extract links + rxStrict := xurls.Strict() + url := rxStrict.FindString(line) + + // Check for empty links and skip them + if url == " " || len(url) == 0 { + continue + } + + // Look for http & https only + if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { + // Ignore localhost + if !strings.Contains(url, "localhost") && !strings.Contains(url, "127.0.0.1") { + urls = append(urls, url) + } + } + } + + return urls +} + +func lintURLs(filepathToURLs map[string][]string, ctx context.Context) (string, error) { + // Setup parallel checking for links + g, _ := errgroup.WithContext(ctx) + + var ( + lock sync.Mutex + output bytes.Buffer + found bool + ) + + for filePath, urls := range filepathToURLs { + filePath := filePath + for _, url := range urls { + url := url + g.Go(func() error { + if err := checkUrl(url); err != nil { + lock.Lock() + if !found { + output.WriteString("Remote links that need checking:\n") + found = true + } + + output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", url, filePath)) + lock.Unlock() + } + + return nil + }) + } + } + + // Check for possible thread errors + if err := g.Wait(); err != nil { + return "", err + } + + if found { + return output.String(), errFound404Links + } + + return "", nil +} + +// checkUrl checks if a URL is a 404 +func checkUrl(url string) error { + // Attempt to retrieve the HTTP header + resp, err := http.Get(url) + if err != nil || resp.StatusCode == http.StatusNotFound { + return err404Link + } + + // Ensure the response body is closed properly + cleanup := func(Body io.ReadCloser) error { + if err := Body.Close(); err != nil { + return fmt.Errorf("could not close response properly: %w", err) + } + + return nil + } + + return cleanup(resp.Body) +} diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go index 9d0c21c5229..7c041af5a72 100644 --- a/misc/genstd/genstd.go +++ b/misc/genstd/genstd.go @@ -1,5 +1,13 @@ -// Command genstd provides static code generation for standard library native -// bindings. +// Command genstd is a code-generator to create meta-information for the Gno +// standard libraries. +// +// All the packages in the standard libraries are parsed and relevant +// information is collected; the file is then generated followingthe template +// available in ./template.tmpl +// +// genstd is responsible for linking natively bound functions, a FFI from Gno +// functions to Go implementations, and calculating the initialization order +// of the standard libraries. package main import ( @@ -10,7 +18,6 @@ import ( "io/fs" "os" "path/filepath" - "strconv" "strings" "text/template" @@ -28,6 +35,8 @@ func main() { } } +const outputFile = "generated.go" + func _main(stdlibsPath string) error { stdlibsPath = filepath.Clean(stdlibsPath) if s, err := os.Stat(stdlibsPath); err != nil { @@ -45,17 +54,19 @@ func _main(stdlibsPath string) error { // Link up each Gno function with its matching Go function. mappings := linkFunctions(pkgs) + initOrder := sortPackages(pkgs) // Create generated file. - f, err := os.Create("native.go") + f, err := os.Create(outputFile) if err != nil { - return fmt.Errorf("create native.go: %w", err) + return fmt.Errorf("create "+outputFile+": %w", err) } defer f.Close() // Execute template. td := &tplData{ - Mappings: mappings, + Mappings: mappings, + InitOrder: initOrder, } if err := tpl.Execute(f, td); err != nil { return fmt.Errorf("execute template: %w", err) @@ -73,10 +84,15 @@ func _main(stdlibsPath string) error { } type pkgData struct { - importPath string - fsDir string + importPath string + fsDir string + + // for matching native functions gnoBodyless []funcDecl goExported []funcDecl + + // for determining initialization order + imports map[string]struct{} } type funcDecl struct { @@ -92,12 +108,12 @@ func addImports(fds []*ast.FuncDecl, imports []*ast.ImportSpec) []funcDecl { return r } -// walkStdlibs does a BFS walk through the given directory, expected to be a +// walkStdlibs does walks through the given directory, expected to be a // "stdlib" directory, parsing and keeping track of Go and Gno functions of // interest. func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { pkgs := make([]*pkgData, 0, 64) - err := filepath.WalkDir(stdlibsPath, func(fpath string, d fs.DirEntry, err error) error { + err := WalkDir(stdlibsPath, func(fpath string, d fs.DirEntry, err error) error { // skip dirs and top-level directory. if d.IsDir() || filepath.Dir(fpath) == stdlibsPath { return nil @@ -114,12 +130,14 @@ func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { dir := filepath.Dir(fpath) var pkg *pkgData - // because of bfs, we know that if we've already been in this directory - // in a previous file, it must be in the last entry of pkgs. + // if we've already been in this directory in a previous file, it must + // be in the last entry of pkgs, as all files in a directory are + // processed together. if len(pkgs) == 0 || pkgs[len(pkgs)-1].fsDir != dir { pkg = &pkgData{ - importPath: strings.ReplaceAll(strings.TrimPrefix(dir, stdlibsPath+"/"), string(filepath.Separator), "/"), + importPath: strings.TrimPrefix(strings.ReplaceAll(dir, string(filepath.Separator), "/"), stdlibsPath+"/"), fsDir: dir, + imports: make(map[string]struct{}), } pkgs = append(pkgs, pkg) } else { @@ -130,16 +148,26 @@ func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { if err != nil { return err } + if ext == ".go" { // keep track of exported function declarations. // warn about all exported type, const and var declarations. if exp := filterExported(f); len(exp) > 0 { pkg.goExported = append(pkg.goExported, addImports(exp, f.Imports)...) } - } else if bd := filterBodylessFuncDecls(f); len(bd) > 0 { + return nil + } + + // ext == ".gno" + if bd := filterBodylessFuncDecls(f); len(bd) > 0 { // gno file -- keep track of function declarations without body. pkg.gnoBodyless = append(pkg.gnoBodyless, addImports(bd, f.Imports)...) } + for _, imp := range f.Imports { + impVal := mustUnquote(imp.Path.Value) + pkg.imports[impVal] = struct{}{} + } + return nil }) return pkgs, err @@ -181,11 +209,13 @@ var tpl = template.Must(template.New("").Parse(templateText)) // tplData is the data passed to the template. type tplData struct { - Mappings []mapping + Mappings []mapping + InitOrder []string } type tplImport struct{ Name, Path string } +// Imports returns the packages that the resulting generated files should import. func (t tplData) Imports() (res []tplImport) { add := func(path string) { for _, v := range res { @@ -199,11 +229,7 @@ func (t tplData) Imports() (res []tplImport) { add(m.GoImportPath) // There might be a bit more than we need - but we run goimports to fix that. for _, v := range m.goImports { - s, err := strconv.Unquote(v.Path.Value) - if err != nil { - panic(fmt.Errorf("could not unquote go import string literal: %s", v.Path.Value)) - } - add(s) + add(mustUnquote(v.Path.Value)) } } return diff --git a/misc/genstd/genstd_test.go b/misc/genstd/genstd_test.go new file mode 100644 index 00000000000..00133f5dc18 --- /dev/null +++ b/misc/genstd/genstd_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIntegration(t *testing.T) { + chdir(t, "testdata/integration") + skipExternalTools = true + + err := _main(".") + t.Cleanup(func() { os.Remove("generated.go") }) + require.NoError(t, err) + + got, err := os.ReadFile("generated.go") + require.NoError(t, err) + + want, err := os.ReadFile("generated.go.golden") + require.NoError(t, err) + + assert.Equal(t, string(want), string(got)) +} diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 69aad8af3a6..8b70e2f512d 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -5,7 +5,6 @@ import ( "go/ast" "go/types" "path" - "strconv" ) const gnoPackagePath = "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -248,10 +247,7 @@ func (m *mapping) typesEqual(gnoe, goe ast.Expr) error { // returns full import path from package ident func resolveImport(imports []*ast.ImportSpec, ident string) string { for _, i := range imports { - s, err := strconv.Unquote(i.Path.Value) - if err != nil { - panic(fmt.Errorf("could not unquote import path literal: %s", i.Path.Value)) - } + s := mustUnquote(i.Path.Value) // TODO: for simplicity, if i.Name is nil we assume the name to be == // to the last part of the import path. diff --git a/misc/genstd/package_sort.go b/misc/genstd/package_sort.go new file mode 100644 index 00000000000..575f56d9506 --- /dev/null +++ b/misc/genstd/package_sort.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "slices" + "strings" + + "golang.org/x/exp/maps" +) + +// mostly for the "testing" package, these only exist as Gno native injections +var nativeInjections = []string{ + "fmt", + "os", + "encoding/json", +} + +// sortPackages sorts pkgs into their initialization order. +func sortPackages(pkgs []*pkgData) []string { + res := make([]string, 0, len(pkgs)) + + var process func(pkg *pkgData, chain []string) + process = func(pkg *pkgData, chain []string) { + if idx := slices.Index(chain, pkg.importPath); idx != -1 { + panic( + fmt.Errorf("cyclical package initialization on %q (%s -> %s)", + pkg.importPath, + strings.Join(chain[idx:], " -> "), + pkg.importPath, + ), + ) + } + // for a deterministic result, sort the imports. + imports := maps.Keys(pkg.imports) + slices.Sort(imports) + for _, imp := range imports { + if slices.Contains(res, imp) { + continue + } + if pkg.importPath == "testing" && + slices.Contains(nativeInjections, imp) { + continue + } + + // import does not exist; find it in pkg and process it. + idx := slices.IndexFunc(pkgs, func(p *pkgData) bool { return p.importPath == imp }) + if idx == -1 { + panic(fmt.Errorf("package does not exist: %q (while processing imports from %q)", imp, pkg.importPath)) + } + process(pkgs[idx], append(chain, pkg.importPath)) + } + res = append(res, pkg.importPath) + } + + // 16 is a guess of maximum depth of dependency initialization + ch := make([]string, 0, 16) + for _, pkg := range pkgs { + if !slices.Contains(res, pkg.importPath) { + process(pkg, ch) + } + } + + return res +} diff --git a/misc/genstd/package_sort_test.go b/misc/genstd/package_sort_test.go new file mode 100644 index 00000000000..79de5bb3bf3 --- /dev/null +++ b/misc/genstd/package_sort_test.go @@ -0,0 +1,126 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_sortPackages(t *testing.T) { + t.Parallel() + + imports := func(imps ...string) map[string]struct{} { + m := make(map[string]struct{}, len(imps)) + for _, imp := range imps { + m[imp] = struct{}{} + } + return m + } + + tt := []struct { + name string + pkgs []*pkgData + output []string + panic string + }{ + { + name: "independent", + pkgs: []*pkgData{ + {importPath: "a"}, + {importPath: "b"}, + }, + output: []string{"a", "b"}, + }, + { + name: "importExists", + pkgs: []*pkgData{ + {importPath: "a"}, + {importPath: "b", imports: imports("a")}, + }, + output: []string{"a", "b"}, + }, + { + name: "reversed", + pkgs: []*pkgData{ + {importPath: "a", imports: imports("b")}, + {importPath: "b"}, + }, + output: []string{"b", "a"}, + }, + { + name: "testingSpecial", + pkgs: []*pkgData{ + {importPath: "testing", imports: imports("fmt", "os", "b")}, + {importPath: "b"}, + }, + output: []string{"b", "testing"}, + }, + + { + name: "cyclical0", + pkgs: []*pkgData{ + {importPath: "a", imports: imports("a")}, + }, + panic: `cyclical package initialization on "a" (a -> a)`, + }, + { + name: "cyclical1", + pkgs: []*pkgData{ + {importPath: "a", imports: imports("b")}, + {importPath: "b", imports: imports("a")}, + }, + panic: `cyclical package initialization on "a" (a -> b -> a)`, + }, + { + name: "cyclical2", + pkgs: []*pkgData{ + {importPath: "a", imports: imports("b")}, + {importPath: "b", imports: imports("c")}, + {importPath: "c", imports: imports("a")}, + }, + panic: `cyclical package initialization on "a" (a -> b -> c -> a)`, + }, + { + name: "cyclical1_indirect", + pkgs: []*pkgData{ + {importPath: "a", imports: imports("b")}, + {importPath: "b", imports: imports("c")}, + {importPath: "c", imports: imports("b")}, + }, + panic: `cyclical package initialization on "b" (b -> c -> b)`, + }, + + { + name: "notFound", + pkgs: []*pkgData{ + {importPath: "a", imports: imports("b")}, + }, + panic: `package does not exist: "b" (while processing imports from "a")`, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + if tc.panic != "" { + assert.PanicsWithError(t, tc.panic, func() { + sortPackages(tc.pkgs) + }) + return + } + result := sortPackages(tc.pkgs) + assert.Equal(t, tc.output, result) + }) + } +} + +func Test_sortPackages_integration(t *testing.T) { + chdir(t, "testdata/sortPackages") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + order := sortPackages(pkgs) + assert.Equal(t, []string{"b", "a"}, order) +} diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index f2cad0a851b..2d714589ef6 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -12,15 +12,25 @@ import ( {{- end }} ) -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ {{- range $i, $m := .Mappings }} { {{ printf "%q" $m.GnoImportPath }}, @@ -36,6 +46,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r.GnoType }})}, {{- end }} }, + {{ if $m.MachineParam }}true{{ else }}false{{ end }}, func(m *gno.Machine) { {{ if $m.Params -}} b := m.LastBlock() @@ -86,3 +97,20 @@ var nativeFuncs = [...]nativeFunc{ }, {{- end }} } + +var initOrder = [...]string{ +{{- range .InitOrder }} + {{ printf "%q" . }}, +{{- end }} +} + +// InitOrder returns the initialization order of the standard libraries. +// This is calculated starting from the list of all standard libraries and +// iterating through each: if a package depends on an unitialized package, that +// is processed first, and so on recursively; matching the behaviour of Go's +// [program initialization]. +// +// [program initialization]: https://go.dev/ref/spec#Program_initialization +func InitOrder() []string { + return initOrder[:] +} diff --git a/misc/genstd/testdata/integration/bytes/bytes.gno b/misc/genstd/testdata/integration/bytes/bytes.gno new file mode 100644 index 00000000000..7c1aaaa60f6 --- /dev/null +++ b/misc/genstd/testdata/integration/bytes/bytes.gno @@ -0,0 +1,14 @@ +package bytes + +import "io" + +type Buffer struct{ b []byte } + +func (b *Buffer) Write(buf []byte) (int, error) { + b.b = append(b.b, buf...) + return len(buf), nil +} + +var _ io.Writer = (*Buffer)(nil) + +func myNative() int // injected diff --git a/misc/genstd/testdata/integration/bytes/bytes.go b/misc/genstd/testdata/integration/bytes/bytes.go new file mode 100644 index 00000000000..3ecf5e49e05 --- /dev/null +++ b/misc/genstd/testdata/integration/bytes/bytes.go @@ -0,0 +1,9 @@ +package bytes + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X_myNative(m *gno.Machine) gno.TypedValue { + return gno.TypedValue{} +} diff --git a/misc/genstd/testdata/integration/generated.go.golden b/misc/genstd/testdata/integration/generated.go.golden new file mode 100644 index 00000000000..d0be334480f --- /dev/null +++ b/misc/genstd/testdata/integration/generated.go.golden @@ -0,0 +1,69 @@ +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// To regenerate it, run `go generate` from this directory. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + repo_misc_genstd_testdata_integration_bytes "github.com/gnolang/gno/misc/genstd/testdata/integration/bytes" + vm_pkg_gnolang "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) +} + +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ + { + "bytes", + "myNative", + []gno.FieldTypeExpr{ + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int")}, + }, + true, + func(m *gno.Machine) { + + + r0 := repo_misc_genstd_testdata_integration_bytes.X_myNative( + m, + ) + + + m.PushValue(r0) + }, + }, +} + +var initOrder = [...]string{ + "io", + "bytes", +} + +// InitOrder returns the initialization order of the standard libraries. +// This is calculated starting from the list of all standard libraries and +// iterating through each: if a package depends on an unitialized package, that +// is processed first, and so on recursively; matching the behaviour of Go's +// [program initialization]. +// +// [program initialization]: https://go.dev/ref/spec#Program_initialization +func InitOrder() []string { + return initOrder[:] +} diff --git a/misc/genstd/testdata/integration/io/io.gno b/misc/genstd/testdata/integration/io/io.gno new file mode 100644 index 00000000000..cda116ea47a --- /dev/null +++ b/misc/genstd/testdata/integration/io/io.gno @@ -0,0 +1,5 @@ +package io + +type Writer interface { + Write([]byte) (int, error) +} diff --git a/misc/genstd/testdata/sortPackages/a/a.gno b/misc/genstd/testdata/sortPackages/a/a.gno new file mode 100644 index 00000000000..aef77064ce1 --- /dev/null +++ b/misc/genstd/testdata/sortPackages/a/a.gno @@ -0,0 +1,3 @@ +package a + +import "b" diff --git a/misc/genstd/testdata/sortPackages/b/b.gno b/misc/genstd/testdata/sortPackages/b/b.gno new file mode 100644 index 00000000000..e0836a88394 --- /dev/null +++ b/misc/genstd/testdata/sortPackages/b/b.gno @@ -0,0 +1 @@ +package b diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 061a9604c67..025fe4b673e 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -7,17 +7,24 @@ import ( "os/exec" "path" "path/filepath" + "strconv" "strings" "sync" ) +// for tests +var skipExternalTools bool + func runTool(importPath string) error { + if skipExternalTools { + return nil + } shortName := path.Base(importPath) gr := gitRoot() cmd := exec.Command( "go", "run", "-modfile", filepath.Join(gr, "misc/devdeps/go.mod"), - importPath, "-w", "native.go", + importPath, "-w", outputFile, ) _, err := cmd.Output() if err != nil { @@ -117,3 +124,11 @@ func pkgNameFromPath(path string) string { }) return ns + "_" + strings.Join(flds, "_") } + +func mustUnquote(v string) string { + s, err := strconv.Unquote(v) + if err != nil { + panic(fmt.Errorf("could not unquote import path literal: %s", v)) + } + return s +} diff --git a/misc/genstd/walk.go b/misc/genstd/walk.go new file mode 100644 index 00000000000..85d1cbf902d --- /dev/null +++ b/misc/genstd/walk.go @@ -0,0 +1,56 @@ +package main + +import ( + "io/fs" + "os" + "path/filepath" +) + +// WalkDir is a forked version of [filepath.WalkDir]. +// Special case errors are removed (SkipDir/SkipAll), and the walk does +// a first pass through the files and then a second pass through the directories. +func WalkDir(root string, fn fs.WalkDirFunc) error { + info, err := os.Lstat(root) + if err != nil { + err = fn(root, nil, err) + } else { + err = walkDir(root, fs.FileInfoToDirEntry(info), fn) + } + return err +} + +// walkDir recursively descends path, calling walkDirFn. +func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error { + if err := walkDirFn(path, d, nil); err != nil { + return err + } + + entries, err := os.ReadDir(path) + if err != nil { + // Second call, to report ReadDir error. + err = walkDirFn(path, d, err) + if err != nil { + return err + } + } + + // First pass, for files + for _, f := range entries { + if !f.IsDir() { + fullPath := filepath.Join(path, f.Name()) + if err := walkDirFn(fullPath, f, nil); err != nil { + return err + } + } + } + // Second pass, for directories + for _, d := range entries { + if d.IsDir() { + fullPath := filepath.Join(path, d.Name()) + if err := walkDir(fullPath, d, walkDirFn); err != nil { + return err + } + } + } + return nil +} diff --git a/misc/loop/cmd/portal-loop.go b/misc/loop/cmd/portal-loop.go index a3e93aca2fc..bffc072c4ac 100644 --- a/misc/loop/cmd/portal-loop.go +++ b/misc/loop/cmd/portal-loop.go @@ -83,13 +83,13 @@ func StartPortalLoop(ctx context.Context, portalLoop *snapshotter, force bool) e } // 6. Start a new portal loop - container, err := portalLoop.startPortalLoopContainer(context.Background()) + dockerContainer, err := portalLoop.startPortalLoopContainer(context.Background()) if err != nil { return err } - for _, p := range container.Ports { + for _, p := range dockerContainer.Ports { if p.Type == "tcp" && p.PrivatePort == uint16(26657) { - ip := container.NetworkSettings.Networks["portal-loop"].IPAddress + ip := dockerContainer.NetworkSettings.Networks["portal-loop"].IPAddress portalLoop.url = fmt.Sprintf("http://%s:%d", ip, int(p.PrivatePort)) break } diff --git a/misc/loop/cmd/snapshotter.go b/misc/loop/cmd/snapshotter.go index adba5dca170..8ace34c7ba6 100644 --- a/misc/loop/cmd/snapshotter.go +++ b/misc/loop/cmd/snapshotter.go @@ -71,7 +71,7 @@ func NewSnapshotter(dockerClient *client.Client, cfg config) (*snapshotter, erro // pullLatestImage get latest version of the docker image func (s snapshotter) pullLatestImage(ctx context.Context) (bool, error) { - reader, err := s.dockerClient.ImagePull(ctx, "ghcr.io/gnolang/gno", types.ImagePullOptions{}) + reader, err := s.dockerClient.ImagePull(ctx, "ghcr.io/gnolang/gno/gnoland:master", types.ImagePullOptions{}) if err != nil { return false, err } @@ -138,16 +138,17 @@ func (s snapshotter) startPortalLoopContainer(ctx context.Context) (*types.Conta } // Run Docker container - container, err := s.dockerClient.ContainerCreate(ctx, &container.Config{ - Image: "ghcr.io/gnolang/gno", + dockerContainer, err := s.dockerClient.ContainerCreate(ctx, &container.Config{ + Image: "ghcr.io/gnolang/gno/gnoland:master", Labels: map[string]string{ "the-portal-loop": s.containerName, }, + WorkingDir: "/gnoroot", Env: []string{ "MONIKER=the-portal-loop", "GENESIS_BACKUP_FILE=/backups/backup.jsonl", }, - Cmd: []string{"/scripts/start.sh"}, + Entrypoint: []string{"/scripts/start.sh"}, ExposedPorts: nat.PortSet{ "26656/tcp": struct{}{}, "26657/tcp": struct{}{}, @@ -162,19 +163,19 @@ func (s snapshotter) startPortalLoopContainer(ctx context.Context) (*types.Conta Binds: []string{ fmt.Sprintf("%s/scripts:/scripts", s.cfg.hostPWD), fmt.Sprintf("%s/backups:/backups", s.cfg.hostPWD), - fmt.Sprintf("%s:/opt/gno/src/gnoland-data", s.containerName), + fmt.Sprintf("%s:/gnoroot/gnoland-data", s.containerName), }, }, nil, nil, s.containerName) if err != nil { return nil, err } - err = s.dockerClient.NetworkConnect(ctx, "portal-loop", container.ID, nil) + err = s.dockerClient.NetworkConnect(ctx, "portal-loop", dockerContainer.ID, nil) if err != nil { return nil, err } - if err := s.dockerClient.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { + if err := s.dockerClient.ContainerStart(ctx, dockerContainer.ID, types.ContainerStartOptions{}); err != nil { return nil, err } time.Sleep(time.Second * 5) @@ -184,7 +185,7 @@ func (s snapshotter) startPortalLoopContainer(ctx context.Context) (*types.Conta return nil, err } for _, c := range containers { - if c.ID == container.ID { + if c.ID == dockerContainer.ID { return &c, nil } } @@ -204,10 +205,14 @@ func (s snapshotter) backupTXs(ctx context.Context, rpcURL string) error { defer instanceBackupFile.Close() w := legacy.NewWriter(instanceBackupFile) - // client := http.NewClient(s.cfg.rpcAddr) - client := http.NewClient(rpcURL) - backupService := backup.NewService(client, w) + // Create the tx-archive backup service + c, err := http.NewClient(rpcURL) + if err != nil { + return fmt.Errorf("could not create tx-archive client, %w", err) + } + + backupService := backup.NewService(c, w) // Run the backup service if backupErr := backupService.ExecuteBackup(ctx, cfg); backupErr != nil { diff --git a/misc/loop/docker-compose.production.yml b/misc/loop/docker-compose.production.yml index f04b477c5b3..2afa013de64 100644 --- a/misc/loop/docker-compose.production.yml +++ b/misc/loop/docker-compose.production.yml @@ -44,14 +44,14 @@ services: - ./traefik/letsencrypt:/letsencrypt gnoweb: - image: ghcr.io/gnolang/gno/gnoweb-slim + image: ghcr.io/gnolang/gno/gnoweb:master restart: unless-stopped env_file: ".env" entrypoint: - gnoweb - --bind=0.0.0.0:8888 - --remote=traefik:26657 - - --faucet-url=https://faucet-api.portal.gno.land + - --faucet-url=https://faucet-api.gno.land - --captcha-site=$CAPTCHA_SITE_KEY - --with-analytics - --help-chainid=portal-loop @@ -62,7 +62,7 @@ services: com.centurylinklabs.watchtower.enable: "true" traefik.enable: "true" traefik.http.routers.gnoweb.entrypoints: "web,websecure" - traefik.http.routers.gnoweb.rule: "Host(`gno.land`)" + traefik.http.routers.gnoweb.rule: "Host(`gno.land`) || Host(`www.gno.land`)" traefik.http.routers.gnoweb.tls: "true" traefik.http.routers.gnoweb.tls.certresolver: "le" @@ -117,7 +117,7 @@ services: - "com.centurylinklabs.watchtower.enable=true" autocounterd: - image: ghcr.io/albttx/gno/autocounterd + image: ghcr.io/gnolang/gno/autocounterd restart: unless-stopped env_file: ".env" command: diff --git a/misc/loop/docker-compose.yml b/misc/loop/docker-compose.yml index 1475b6699fe..eba0f55e787 100644 --- a/misc/loop/docker-compose.yml +++ b/misc/loop/docker-compose.yml @@ -11,7 +11,7 @@ networks: services: traefik: - image: "traefik:v2.10" + image: "traefik:v2.11" restart: unless-stopped command: - "--api.insecure=true" @@ -31,7 +31,7 @@ services: - ./traefik:/etc/traefik/configs gnoweb: - image: ghcr.io/gnolang/gno/gnoweb-slim + image: ghcr.io/gnolang/gno/gnoweb:master restart: unless-stopped networks: - portal-loop diff --git a/misc/loop/go.mod b/misc/loop/go.mod index edf64092736..be37c21f5c9 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,47 +1,43 @@ module loop -go 1.21 +go 1.22 + +toolchain go1.22.4 require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 - github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278 - github.com/gnolang/tx-archive v0.1.1 + github.com/gnolang/gno v0.1.0-nightly.20240707 + github.com/gnolang/tx-archive v0.3.0 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 ) require ( dario.cat/mergo v1.0.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/btcsuite/btcd/btcutil v1.1.3 // indirect - github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect + github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/dgraph-io/badger/v3 v3.2103.5 // indirect - github.com/dgraph-io/ristretto v0.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/dustin/go-humanize v1.0.0 // indirect - github.com/gnolang/goleveldb v0.0.9 // indirect github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.1.2 // indirect - github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/flatbuffers v1.12.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/jaekwon/testify v1.6.1 // indirect - github.com/jmhodges/levigo v1.0.0 // indirect - github.com/klauspost/compress v1.12.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/linxGnu/grocksdb v1.8.11 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -54,23 +50,37 @@ require ( github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect - github.com/rs/cors v1.10.1 // indirect - github.com/stretchr/testify v1.8.4 // indirect - github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect - go.etcd.io/bbolt v1.3.8 // indirect - go.opencensus.io v0.22.5 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.26.0 // indirect + github.com/rs/cors v1.11.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.18.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // 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 c9dee625290..2ad488a5f25 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -1,33 +1,28 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= -github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= +github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= -github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -37,18 +32,14 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= +github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -57,15 +48,9 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= -github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/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= @@ -76,95 +61,66 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh 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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278 h1:CxF7gG3iqSeYVygTSYsB7Beg+Fpvka06TuTI2a0p+6s= -github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278/go.mod h1:mOhpUTFaKk5CQj90qmjWfI9po2eapqziEu4D+fAtisc= -github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE= -github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E= +github.com/gnolang/gno v0.1.0-nightly.20240707 h1:ez1BtiwRuqRHRxvqyKDbUbNtUBYEjXwSHqRu6m347os= +github.com/gnolang/gno v0.1.0-nightly.20240707/go.mod h1:BTaBNeaoY/W95NN6QA4RCoQ6Z7mi8M+Zb1I1wMWGg2w= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= -github.com/gnolang/tx-archive v0.1.1 h1:maVdRFsc1ptVhwVw1p5scvu2Rus8Yk3o9qlss5+SRCw= -github.com/gnolang/tx-archive v0.1.1/go.mod h1:MrUmRaU6GB9tOPy+5pCe/x1z1fGYtAypVJzKOExeUHY= +github.com/gnolang/tx-archive v0.3.0 h1:5Fr39yAT7nnAPKvcmKmBT+oPiBhMhA0aUAIEeXrYG4I= +github.com/gnolang/tx-archive v0.3.0/go.mod h1:WDgxSZibE7LkGdiVjkU/lhA35xyXjrSkZp6kwuTvSSw= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= -github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw= -github.com/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= -github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/linxGnu/grocksdb v1.8.11 h1:BGol9e5gB1BrsTvOxloC88pe70TCqgrfLNwkyWW0kD8= -github.com/linxGnu/grocksdb v1.8.11/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -186,7 +142,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= @@ -203,95 +158,84 @@ 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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -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= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/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/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -300,57 +244,49 @@ 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.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -360,4 +296,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/misc/loop/scripts/start.sh b/misc/loop/scripts/start.sh index 1b52f6f03f5..76869ccb4bd 100755 --- a/misc/loop/scripts/start.sh +++ b/misc/loop/scripts/start.sh @@ -11,19 +11,26 @@ GENESIS_BACKUP_FILE=${GENESIS_BACKUP_FILE:-""} SEEDS=${SEEDS:-""} PERSISTENT_PEERS=${PERSISTENT_PEERS:-""} -echo "" >> /opt/gno/src/gno.land/genesis/genesis_txs.jsonl -cat ${GENESIS_BACKUP_FILE} >> /opt/gno/src/gno.land/genesis/genesis_txs.jsonl - -gnoland start \ - --chainid="${CHAIN_ID}" \ - --skip-start=true \ - --skip-failing-genesis-txs - -sed -i "s#^moniker = \".*\"#moniker = \"${MONIKER}\"#" ./gnoland-data/config/config.toml -sed -i "s#laddr = \".*:26656\"#laddr = \"${P2P_LADDR}\"#" ./gnoland-data/config/config.toml -sed -i "s#laddr = \".*:26657\"#laddr = \"${RPC_LADDR}\"#" ./gnoland-data/config/config.toml - -sed -i "s#seeds = \".*\"#seeds = \"${SEEDS}\"#" ./gnoland-data/config/config.toml -sed -i "s#persistent_peers = \".*\"#persistent_peers = \"${PERSISTENT_PEERS}\"#" ./gnoland-data/config/config.toml - -exec gnoland start --skip-failing-genesis-txs +echo "" >> /gnoroot/gno.land/genesis/genesis_txs.jsonl +cat ${GENESIS_BACKUP_FILE} >> /gnoroot/gno.land/genesis/genesis_txs.jsonl + +# Initialize the secrets +gnoland secrets init + +# Initialize the configuration +gnoland config init + +# Set the config values +gnoland config set moniker "${MONIKER}" +gnoland config set rpc.laddr "${RPC_LADDR}" +gnoland config set p2p.laddr "${P2P_LADDR}" +gnoland config set p2p.seeds "${SEEDS}" +gnoland config set p2p.persistent_peers "${PERSISTENT_PEERS}" + +# Running a lazy init will generate a fresh genesis.json, with +# the previously generated secrets. We do this to avoid CLI magic from config +# reading and piping to the gnoland genesis commands +exec gnoland start \ + --chainid="${CHAIN_ID}" \ + --lazy \ + --skip-failing-genesis-txs diff --git a/misc/loop/traefik/gno.yml b/misc/loop/traefik/gno.yml index 59ed91bf62c..7e65930889d 100644 --- a/misc/loop/traefik/gno.yml +++ b/misc/loop/traefik/gno.yml @@ -26,4 +26,4 @@ http: gno-portal-loop: loadBalancer: servers: - - url: "http://172.42.0.5:26657" + - url: "http://172.42.0.2:26657" diff --git a/misc/telemetry/Makefile b/misc/telemetry/Makefile new file mode 100644 index 00000000000..956c4f11190 --- /dev/null +++ b/misc/telemetry/Makefile @@ -0,0 +1,11 @@ +.PHONY: up +up: + docker compose up -d --build + +.PHONY: down +down: + docker compose down + +.PHONY: clean +clean: + docker compose down -v \ No newline at end of file diff --git a/misc/telemetry/README.md b/misc/telemetry/README.md new file mode 100644 index 00000000000..41628cc5f51 --- /dev/null +++ b/misc/telemetry/README.md @@ -0,0 +1,56 @@ +## Overview + +The purpose of this Telemetry documentation is to showcase the different node metrics exposed by the Gno node through +OpenTelemetry, without having to do extraneous setup. + +The containerized setup is the following: + +- Grafana dashboard +- Prometheus +- OpenTelemetry collector (separate service that needs to run) +- Single Gnoland node, with 1s block times and configured telemetry (enabled) +- Supernova process that simulates load periodically (generates network traffic) + +## Starting the containers + +### Step 1: Spinning up Docker + +Make sure you have Docker installed and running on your system. After that, within the `misc/telemetry` folder run the +following command: + +```shell +make up +``` + +This will build out the required Docker images for this simulation, and start the services + +### Step 2: Open Grafana + +When you've verified that the `telemetry` containers are up and running, head on over to http://localhost:3000 to open +the Grafana dashboard. + +Default login details: + +``` +username: admin +password: admin +``` + +After you've logged in (you can skip setting a new password), on the left hand side, click on +`Dashboards -> Gno -> Gno Node Metrics`: +![Grafana](assets/grafana-1.jpeg) + +This will open up the predefined Gno Metrics dashboards (added for ease of use) : +![Metrics Dashboard](assets/grafana-2.jpeg) + +Periodically, these metrics will be updated as the `supernova` process is simulating network traffic. + +### Step 3: Stopping the cluster + +To stop the cluster, you can run: + +```shell +make down +``` + +which will stop the Docker containers. Additionally, you can delete the Docker volumes with `make clean`. \ No newline at end of file diff --git a/misc/telemetry/assets/grafana-1.jpeg b/misc/telemetry/assets/grafana-1.jpeg new file mode 100644 index 00000000000..f29ca02609e Binary files /dev/null and b/misc/telemetry/assets/grafana-1.jpeg differ diff --git a/misc/telemetry/assets/grafana-2.jpeg b/misc/telemetry/assets/grafana-2.jpeg new file mode 100644 index 00000000000..a08e8317078 Binary files /dev/null and b/misc/telemetry/assets/grafana-2.jpeg differ diff --git a/misc/telemetry/collector/collector.yaml b/misc/telemetry/collector/collector.yaml new file mode 100644 index 00000000000..d258dd025bb --- /dev/null +++ b/misc/telemetry/collector/collector.yaml @@ -0,0 +1,22 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + +processors: + batch: + +exporters: + prometheus: + endpoint: collector:8090 + +service: + telemetry: + logs: + level: "debug" + pipelines: + metrics: + receivers: [ otlp ] + processors: [ batch ] + exporters: [ prometheus ] diff --git a/misc/telemetry/docker-compose.yml b/misc/telemetry/docker-compose.yml new file mode 100644 index 00000000000..91c2ea3471d --- /dev/null +++ b/misc/telemetry/docker-compose.yml @@ -0,0 +1,65 @@ +services: + collector: + image: otel/opentelemetry-collector-contrib:latest + ports: + - "4317:4317" + - "4318:4318" + - "8090" + volumes: + - ./collector/collector.yaml:/etc/otelcol-contrib/config.yaml + networks: + - gnoland-net + prometheus: + image: prom/prometheus:latest + command: + - "--enable-feature=remote-write-receiver" + - "--config.file=/etc/prometheus/prometheus.yml" + ports: + - "9090:9090" + volumes: + - prometheus:/prometheus + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + networks: + - gnoland-net + grafana: + image: grafana/grafana-enterprise + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml + - ./grafana/dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml + - ./grafana/gno-dashboards.json:/var/lib/grafana/dashboards/gno-dashboards.json + ports: + - "3000:3000" + networks: + - gnoland-net + gnoland: + build: + context: ./gnoland + dockerfile: Dockerfile + ports: + - "26657:26657" + networks: + - gnoland-net + supernova: + build: + dockerfile: supernova.Dockerfile + args: + supernova_version: v1.2.1 + command: > + -sub-accounts 10 -transactions 200 -url http://gnoland:26657 + -mnemonic "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" + restart: always + networks: + - gnoland-net + +networks: + gnoland-net: + driver: bridge + +volumes: + prometheus: + driver: local + grafana_data: + driver: local + gnoland: + driver: local diff --git a/misc/telemetry/gnoland/Dockerfile b/misc/telemetry/gnoland/Dockerfile new file mode 100644 index 00000000000..c8a89e1a634 --- /dev/null +++ b/misc/telemetry/gnoland/Dockerfile @@ -0,0 +1,13 @@ +# Use the existing gno image as the base image +FROM ghcr.io/gnolang/gno/gnoland:master AS base + +# Copy the setup script into the container +COPY ./setup.sh . + +# Make the script executable +RUN chmod +x ./setup.sh + +# Run the setup +ENTRYPOINT ["sh"] + +CMD ["./setup.sh"] \ No newline at end of file diff --git a/misc/telemetry/gnoland/setup.sh b/misc/telemetry/gnoland/setup.sh new file mode 100644 index 00000000000..12cc418ac67 --- /dev/null +++ b/misc/telemetry/gnoland/setup.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Initialize the node config +gnoland config init --config-path /gnoroot/gnoland-data/config/config.toml + +# Set the block time to 1s +gnoland config set --config-path /gnoroot/gnoland-data/config/config.toml consensus.timeout_commit 1s + +# Set the listen address +gnoland config set --config-path /gnoroot/gnoland-data/config/config.toml rpc.laddr tcp://0.0.0.0:26657 + +# Enable the metrics +gnoland config set --config-path /gnoroot/gnoland-data/config/config.toml telemetry.enabled true + +# Set the metrics exporter endpoint +gnoland config set --config-path /gnoroot/gnoland-data/config/config.toml telemetry.exporter_endpoint collector:4317 + +# Start the Gnoland node (lazy will init the genesis.json and secrets) +gnoland start --lazy \ No newline at end of file diff --git a/misc/telemetry/grafana/dashboards.yaml b/misc/telemetry/grafana/dashboards.yaml new file mode 100644 index 00000000000..6a70278b8a1 --- /dev/null +++ b/misc/telemetry/grafana/dashboards.yaml @@ -0,0 +1,8 @@ +apiVersion: 1 + +providers: + - name: Gno Node Metrics + folder: Gno + type: file + options: + path: /var/lib/grafana/dashboards \ No newline at end of file diff --git a/misc/telemetry/grafana/datasources.yaml b/misc/telemetry/grafana/datasources.yaml new file mode 100644 index 00000000000..917b8a544f1 --- /dev/null +++ b/misc/telemetry/grafana/datasources.yaml @@ -0,0 +1,13 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + orgId: 1 + url: http://prometheus:9090 + basicAuth: false + isDefault: false + version: 1 + editable: true + uid: prometheus diff --git a/misc/telemetry/grafana/gno-dashboards.json b/misc/telemetry/grafana/gno-dashboards.json new file mode 100644 index 00000000000..58373bfb1ee --- /dev/null +++ b/misc/telemetry/grafana/gno-dashboards.json @@ -0,0 +1,1360 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "All Gno node metrics", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "panels": [ + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 23, + "panels": [], + "title": "VM", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 24, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "vm_query_errors_counter", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total Number of VM Query Errors", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 25, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "vm_query_calls_counter", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total Number of VM Query Calls", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 26, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(vm_gas_used_hist_sum[10m])/rate(vm_gas_used_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Gas Used by VM execution [10min]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 27, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(vm_cpu_cycles_hist_sum[10m])/rate(vm_cpu_cycles_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average CPU Cycles in VM execution [10min]", + "type": "stat" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 15, + "title": "Mempool", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 16, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(num_mempool_txs_hist_sum[10m])/rate(num_mempool_txs_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Valid Mempool Txs", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 17, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(num_cached_txs_hist_sum[10m])/rate(num_cached_txs_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Cached Mempool Txs", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 11, + "panels": [], + "title": "Networking", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(inbound_peers_hist_sum[10m])/rate(inbound_peers_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Inbound Peer Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": -5, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(outbound_peers_hist_sum[10m])/rate(outbound_peers_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Outbound Peer Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": -5, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 6, + "y": 35 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(dialing_peers_hist_sum[10m])/rate(dialing_peers_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Dialing Peer Count", + "type": "stat" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 43 + }, + "id": 9, + "title": "Consensus", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 44 + }, + "id": 18, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(validator_count_hist_sum[10m])/rate(validator_count_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Validator Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 44 + }, + "id": 19, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(validator_vp_hist_sum[10m])/rate(validator_vp_hist_count[10m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Average Total Validator Voting Power", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "red", + "value": 10 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 52 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(build_block_hist_milliseconds_sum[10m])/rate(build_block_hist_milliseconds_count[10m])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average Block Build Time [10min]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 52 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(block_interval_hist_seconds_sum[10m])/rate(block_interval_hist_seconds_count[10m])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average Block Interval [10min]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 60 + }, + "id": 20, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(block_txs_hist_bucket\n) by (le)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average Block Tx Count [10min]", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 60 + }, + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(block_size_hist_B_sum[10m])/rate(block_size_hist_B_count[10m])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average Block Size [10min]", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 68 + }, + "id": 8, + "panels": [], + "title": "Misc", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1000 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 69 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(broadcast_tx_hist_milliseconds_sum[10m])/rate(broadcast_tx_hist_milliseconds_count[10m])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average Transaction Broadcast Duration [10min]", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 7, + "panels": [], + "title": "JSON-RPC", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 2000 + }, + { + "color": "red", + "value": 5000 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 78 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(http_request_time_hist_milliseconds_sum[10m])/rate(http_request_time_hist_milliseconds_count[10m])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average HTTP Request Round Trip Time [10min]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 2000 + }, + { + "color": "red", + "value": 5000 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 78 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(ws_request_time_hist_milliseconds_sum[10m])/rate(ws_request_time_hist_milliseconds_count[10m])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average WS Request Round Trip Time [10min]", + "type": "stat" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Gno Node Metrics", + "uid": "bdl7d5yogxjb4b", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/misc/telemetry/prometheus/prometheus.yml b/misc/telemetry/prometheus/prometheus.yml new file mode 100644 index 00000000000..f92b2a56da7 --- /dev/null +++ b/misc/telemetry/prometheus/prometheus.yml @@ -0,0 +1,7 @@ +global: + scrape_interval: 5s + +scrape_configs: + - job_name: 'opentelemetry' + static_configs: + - targets: [ 'collector:8090' ] diff --git a/misc/telemetry/supernova.Dockerfile b/misc/telemetry/supernova.Dockerfile new file mode 100644 index 00000000000..67ccbda8047 --- /dev/null +++ b/misc/telemetry/supernova.Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.22-alpine + +ARG supernova_version=latest + +RUN go install github.com/gnolang/supernova/cmd@$supernova_version && mv /go/bin/cmd /go/bin/supernova +RUN export SUPERNOVA_PATH=$(go list -m -f "{{.Dir}}" github.com/gnolang/supernova@${supernova_version}) && \ + mkdir -p /supernova && \ + cp -r $SUPERNOVA_PATH/* /supernova + +WORKDIR /supernova + +ENTRYPOINT ["supernova"] diff --git a/tm2/README.md b/tm2/README.md index c4d6aa8d287..0f6e0052933 100644 --- a/tm2/README.md +++ b/tm2/README.md @@ -2,7 +2,7 @@ **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.** +**Once gno.land is on the mainnet, Tendermint2 will operate independently, including for governance, on https://github.com/tendermint/tendermint2.** ## Mission diff --git a/tm2/pkg/amino/amino.go b/tm2/pkg/amino/amino.go index 1ca3427b85c..ecff955a582 100644 --- a/tm2/pkg/amino/amino.go +++ b/tm2/pkg/amino/amino.go @@ -11,11 +11,12 @@ import ( "runtime" "time" - "github.com/gnolang/gno/tm2/pkg/amino/pkg" - "github.com/gnolang/gno/tm2/pkg/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/gnolang/gno/tm2/pkg/amino/pkg" + "github.com/gnolang/gno/tm2/pkg/errors" ) // Package "pkg" exists So dependencies can create Packages. diff --git a/tm2/pkg/amino/genproto/bindings.go b/tm2/pkg/amino/genproto/bindings.go index 5d3b46c59fc..c1de79caa59 100644 --- a/tm2/pkg/amino/genproto/bindings.go +++ b/tm2/pkg/amino/genproto/bindings.go @@ -1100,6 +1100,27 @@ func _fields(args ...interface{}) *ast.FieldList { } } +const ( + reDGTS = `(?:[0-9]+)` + reHExX = `(?:0[xX][0-9a-fA-F]+)` + rePSCI = `(?:[eE]+?[0-9]+)` + reNSCI = `(?:[eE]-[1-9][0-9]+)` + reASCI = `(?:[eE][-+]?[0-9]+)` +) + +var ( + reIsInt = regexp.MustCompile( + `^-?(?:` + + reDGTS + `|` + + reHExX + `)` + rePSCI + `?$`, + ) + reIsFloat = regexp.MustCompile( + `^-?(?:` + + reDGTS + `\.` + reDGTS + reASCI + `?|` + + reDGTS + reNSCI + `)$`, + ) +) + // Parses simple expressions (but not all). // Useful for parsing strings to ast nodes, like foo.bar["qwe"](), // new(bytes.Buffer), *bytes.Buffer, package.MyStruct{FieldA:1}, numeric @@ -1282,22 +1303,7 @@ func _x(expr string, args ...interface{}) ast.Expr { } } // Numeric int? We do these before dots, because dots are legal in numbers. - const ( - DGTS = `(?:[0-9]+)` - HExX = `(?:0[xX][0-9a-fA-F]+)` - PSCI = `(?:[eE]+?[0-9]+)` - NSCI = `(?:[eE]-[1-9][0-9]+)` - ASCI = `(?:[eE][-+]?[0-9]+)` - ) - isInt, err := regexp.Match( - `^-?(?:`+ - DGTS+`|`+ - HExX+`)`+PSCI+`?$`, - []byte(expr), - ) - if err != nil { - panic("should not happen") - } + isInt := reIsInt.MatchString(expr) if isInt { return &ast.BasicLit{ Kind: token.INT, @@ -1305,15 +1311,7 @@ func _x(expr string, args ...interface{}) ast.Expr { } } // Numeric float? We do these before dots, because dots are legal in floats. - isFloat, err := regexp.Match( - `^-?(?:`+ - DGTS+`\.`+DGTS+ASCI+`?|`+ - DGTS+NSCI+`)$`, - []byte(expr), - ) - if err != nil { - panic("should not happen") - } + isFloat := reIsFloat.MatchString(expr) if isFloat { return &ast.BasicLit{ Kind: token.FLOAT, diff --git a/tm2/pkg/bft/abci/types/abci.proto b/tm2/pkg/bft/abci/types/abci.proto index 99e7a584c2e..15b8ffa219e 100644 --- a/tm2/pkg/bft/abci/types/abci.proto +++ b/tm2/pkg/bft/abci/types/abci.proto @@ -112,6 +112,7 @@ message ResponseInitChain { ResponseBase response_base = 1 [json_name = "ResponseBase"]; ConsensusParams consensus_params = 2 [json_name = "ConsensusParams"]; repeated ValidatorUpdate validators = 3 [json_name = "Validators"]; + repeated ResponseDeliverTx tx_responses = 4 [json_name = "TxResponses"]; } message ResponseQuery { diff --git a/tm2/pkg/bft/abci/types/types.go b/tm2/pkg/bft/abci/types/types.go index 8c2764cb1bd..42376e712a6 100644 --- a/tm2/pkg/bft/abci/types/types.go +++ b/tm2/pkg/bft/abci/types/types.go @@ -159,6 +159,7 @@ type ResponseInitChain struct { ResponseBase ConsensusParams *ConsensusParams Validators []ValidatorUpdate + TxResponses []ResponseDeliverTx } type ResponseQuery struct { diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index a35157c2a4f..f9e9a0cd899 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -48,12 +48,12 @@ type Config struct { BaseConfig `toml:",squash"` // Options for services - RPC *rpc.RPCConfig `toml:"rpc" comment:"##### rpc server configuration options #####"` - P2P *p2p.P2PConfig `toml:"p2p" comment:"##### peer to peer configuration options #####"` - Mempool *mem.MempoolConfig `toml:"mempool" comment:"##### mempool configuration options #####"` - Consensus *cns.ConsensusConfig `toml:"consensus" comment:"##### consensus configuration options #####"` - TxEventStore *eventstore.Config `toml:"tx_event_store" comment:"##### event store #####"` - Telemetry *telemetry.Config `toml:"telemetry" comment:"##### node telemetry #####"` + RPC *rpc.RPCConfig `json:"rpc" toml:"rpc" comment:"##### rpc server configuration options #####"` + P2P *p2p.P2PConfig `json:"p2p" toml:"p2p" comment:"##### peer to peer configuration options #####"` + Mempool *mem.MempoolConfig `json:"mempool" toml:"mempool" comment:"##### mempool configuration options #####"` + Consensus *cns.ConsensusConfig `json:"consensus" toml:"consensus" comment:"##### consensus configuration options #####"` + TxEventStore *eventstore.Config `json:"tx_event_store" toml:"tx_event_store" comment:"##### event store #####"` + Telemetry *telemetry.Config `json:"telemetry" toml:"telemetry" comment:"##### node telemetry #####"` } // DefaultConfig returns a default configuration for a Tendermint node @@ -71,6 +71,36 @@ func DefaultConfig() *Config { type Option func(cfg *Config) +// LoadConfig loads the node configuration from disk +func LoadConfig(root string) (*Config, error) { + // Initialize the config as default + var ( + cfg = DefaultConfig() + configPath = filepath.Join(root, defaultConfigPath) + ) + + if !osm.FileExists(configPath) { + return nil, fmt.Errorf("config file at %q does not exist", configPath) + } + + // Load the configuration + loadedCfg, loadErr := LoadConfigFile(configPath) + if loadErr != nil { + return nil, loadErr + } + + // Merge the loaded config with the default values. + // This is done in case the loaded config is missing values + if err := mergo.Merge(loadedCfg, cfg); err != nil { + return nil, err + } + + // Set the root directory + loadedCfg.SetRootDir(root) + + return loadedCfg, nil +} + // LoadOrMakeConfigWithOptions loads the configuration located in the given // root directory, at [defaultConfigFilePath]. // @@ -142,7 +172,7 @@ func TestConfig() *Config { Mempool: mem.TestMempoolConfig(), Consensus: cns.TestConsensusConfig(), TxEventStore: eventstore.DefaultEventStoreConfig(), - Telemetry: telemetry.TestTelemetryConfig(), + Telemetry: telemetry.DefaultTelemetryConfig(), } } @@ -165,11 +195,11 @@ func (cfg *Config) EnsureDirs() error { return fmt.Errorf("no root directory, %w", err) } - if err := osm.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultConfigDir), DefaultDirPerm); err != nil { return fmt.Errorf("no config directory, %w", err) } - if err := osm.EnsureDir(filepath.Join(rootDir, defaultSecretsDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultSecretsDir), DefaultDirPerm); err != nil { return fmt.Errorf("no secrets directory, %w", err) } @@ -205,18 +235,18 @@ func (cfg *Config) ValidateBasic() error { var ( DefaultDBDir = "db" - defaultConfigDir = "config" - defaultSecretsDir = "secrets" + DefaultConfigDir = "config" + DefaultSecretsDir = "secrets" - defaultConfigFileName = "config.toml" + DefaultConfigFileName = "config.toml" defaultNodeKeyName = "node_key.json" defaultPrivValKeyName = "priv_validator_key.json" defaultPrivValStateName = "priv_validator_state.json" - defaultConfigPath = filepath.Join(defaultConfigDir, defaultConfigFileName) - defaultPrivValKeyPath = filepath.Join(defaultSecretsDir, defaultPrivValKeyName) - defaultPrivValStatePath = filepath.Join(defaultSecretsDir, defaultPrivValStateName) - defaultNodeKeyPath = filepath.Join(defaultSecretsDir, defaultNodeKeyName) + defaultConfigPath = filepath.Join(DefaultConfigDir, DefaultConfigFileName) + defaultPrivValKeyPath = filepath.Join(DefaultSecretsDir, defaultPrivValKeyName) + defaultPrivValStatePath = filepath.Join(DefaultSecretsDir, defaultPrivValStateName) + defaultNodeKeyPath = filepath.Join(DefaultSecretsDir, defaultNodeKeyName) ) // BaseConfig defines the base configuration for a Tendermint node. diff --git a/tm2/pkg/bft/config/toml.go b/tm2/pkg/bft/config/toml.go index 5d8589394a0..ce61ffed5a2 100644 --- a/tm2/pkg/bft/config/toml.go +++ b/tm2/pkg/bft/config/toml.go @@ -69,10 +69,10 @@ func ResetTestRoot(testName string) (*Config, string) { } // ensure config and data subdirs are created - if err := osm.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultConfigDir), DefaultDirPerm); err != nil { panic(err) } - if err := osm.EnsureDir(filepath.Join(rootDir, defaultSecretsDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultSecretsDir), DefaultDirPerm); err != nil { panic(err) } if err := osm.EnsureDir(filepath.Join(rootDir, DefaultDBDir), DefaultDirPerm); err != nil { @@ -81,7 +81,7 @@ func ResetTestRoot(testName string) (*Config, string) { baseConfig := DefaultBaseConfig() configFilePath := filepath.Join(rootDir, defaultConfigPath) - // NOTE: this does not match the behaviour of the Gno.land node. + // NOTE: this does not match the behaviour of the gno.land node. // However, many tests rely on the fact that they can cleanup the directory // by doing RemoveAll on the rootDir; so to keep compatibility with that // behaviour, we place genesis.json in the rootDir. diff --git a/tm2/pkg/bft/consensus/common_test.go b/tm2/pkg/bft/consensus/common_test.go index d4c572c6bda..f657bf3b6d9 100644 --- a/tm2/pkg/bft/consensus/common_test.go +++ b/tm2/pkg/bft/consensus/common_test.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "reflect" "sort" "sync" "testing" @@ -762,3 +763,48 @@ func newPersistentKVStore() abci.Application { func newPersistentKVStoreWithPath(dbDir string) abci.Application { return kvstore.NewPersistentKVStoreApplication(dbDir) } + +// ------------------------------------ + +func ensureDrainedChannels(t *testing.T, channels ...any) { + t.Helper() + + r := recover() + if r == nil { + return + } + + t.Logf("checking for drained channel") + leaks := make(map[string]int) + for _, ch := range channels { + chVal := reflect.ValueOf(ch) + if chVal.Kind() != reflect.Chan { + panic(chVal.Type().Name() + " not a channel") + } + + maxExp := time.After(time.Second * 5) + + // Use a select statement with reflection + cases := []reflect.SelectCase{ + {Dir: reflect.SelectRecv, Chan: chVal}, + {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(maxExp)}, + {Dir: reflect.SelectDefault}, + } + + for { + chosen, recv, recvOK := reflect.Select(cases) + if chosen != 0 || !recvOK { + break + } + + leaks[reflect.TypeOf(recv.Interface()).String()]++ + time.Sleep(time.Millisecond * 500) + } + } + + for leak, count := range leaks { + t.Logf("channel %q: %d events left\n", leak, count) + } + + panic(r) +} diff --git a/tm2/pkg/bft/consensus/config/config.go b/tm2/pkg/bft/consensus/config/config.go index 4a350ff3976..c0b5661a0e5 100644 --- a/tm2/pkg/bft/consensus/config/config.go +++ b/tm2/pkg/bft/consensus/config/config.go @@ -16,29 +16,29 @@ const ( // ConsensusConfig defines the configuration for the Tendermint consensus service, // including timeouts and details about the WAL and the block structure. type ConsensusConfig struct { - RootDir string `toml:"home"` - WALPath string `toml:"wal_file"` - WALDisabled bool `toml:"-"` + RootDir string `json:"home" toml:"home"` + WALPath string `json:"wal_file" toml:"wal_file"` + WALDisabled bool `json:"-" toml:"-"` walFile string // overrides WalPath if set - TimeoutPropose time.Duration `toml:"timeout_propose"` - TimeoutProposeDelta time.Duration `toml:"timeout_propose_delta"` - TimeoutPrevote time.Duration `toml:"timeout_prevote"` - TimeoutPrevoteDelta time.Duration `toml:"timeout_prevote_delta"` - TimeoutPrecommit time.Duration `toml:"timeout_precommit"` - TimeoutPrecommitDelta time.Duration `toml:"timeout_precommit_delta"` - TimeoutCommit time.Duration `toml:"timeout_commit"` + TimeoutPropose time.Duration `json:"timeout_propose" toml:"timeout_propose"` + TimeoutProposeDelta time.Duration `json:"timeout_propose_delta" toml:"timeout_propose_delta"` + TimeoutPrevote time.Duration `json:"timeout_prevote" toml:"timeout_prevote"` + TimeoutPrevoteDelta time.Duration `json:"timeout_prevote_delta" toml:"timeout_prevote_delta"` + TimeoutPrecommit time.Duration `json:"timeout_precommit" toml:"timeout_precommit"` + TimeoutPrecommitDelta time.Duration `json:"timeout_precommit_delta" toml:"timeout_precommit_delta"` + TimeoutCommit time.Duration `json:"timeout_commit" toml:"timeout_commit"` // Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) - SkipTimeoutCommit bool `toml:"skip_timeout_commit" comment:"Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)"` + SkipTimeoutCommit bool `json:"skip_timeout_commit" toml:"skip_timeout_commit" comment:"Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)"` // EmptyBlocks mode and possible interval between empty blocks - CreateEmptyBlocks bool `toml:"create_empty_blocks" comment:"EmptyBlocks mode and possible interval between empty blocks"` - CreateEmptyBlocksInterval time.Duration `toml:"create_empty_blocks_interval"` + CreateEmptyBlocks bool `json:"create_empty_blocks" toml:"create_empty_blocks" comment:"EmptyBlocks mode and possible interval between empty blocks"` + CreateEmptyBlocksInterval time.Duration `json:"create_empty_blocks_interval" toml:"create_empty_blocks_interval"` // Reactor sleep duration parameters - PeerGossipSleepDuration time.Duration `toml:"peer_gossip_sleep_duration" comment:"Reactor sleep duration parameters"` - PeerQueryMaj23SleepDuration time.Duration `toml:"peer_query_maj23_sleep_duration"` + PeerGossipSleepDuration time.Duration `json:"peer_gossip_sleep_duration" toml:"peer_gossip_sleep_duration" comment:"Reactor sleep duration parameters"` + PeerQueryMaj23SleepDuration time.Duration `json:"peer_query_maj_23_sleep_duration" toml:"peer_query_maj23_sleep_duration"` } // DefaultConsensusConfig returns a default configuration for the consensus service diff --git a/tm2/pkg/bft/consensus/replay.go b/tm2/pkg/bft/consensus/replay.go index a423d634c2f..02e6dade72c 100644 --- a/tm2/pkg/bft/consensus/replay.go +++ b/tm2/pkg/bft/consensus/replay.go @@ -307,6 +307,13 @@ func (h *Handshaker) ReplayBlocks( return nil, err } + // Save the results by height + abciResponse := sm.NewABCIResponsesFromNum(int64(len(res.TxResponses))) + copy(abciResponse.DeliverTxs, res.TxResponses) + sm.SaveABCIResponses(h.stateDB, 0, abciResponse) + + // NOTE: we don't save results by tx hash since the transactions are in the AppState opaque type + if stateBlockHeight == 0 { // we only update state when we are in initial state // If the app returned validators or consensus params, update the state. if len(res.Validators) > 0 { diff --git a/tm2/pkg/bft/consensus/replay_test.go b/tm2/pkg/bft/consensus/replay_test.go index 4ba091346f0..aff7316f086 100644 --- a/tm2/pkg/bft/consensus/replay_test.go +++ b/tm2/pkg/bft/consensus/replay_test.go @@ -1131,11 +1131,19 @@ func TestHandshakeUpdatesValidators(t *testing.T) { val, _ := types.RandValidator(true, 10) vals := types.NewValidatorSet([]*types.Validator{val}) - app := &initChainApp{vals: vals.ABCIValidatorUpdates()} + appVals := vals.ABCIValidatorUpdates() + // returns the vals on InitChain + app := initChainApp{ + initChain: func(req abci.RequestInitChain) abci.ResponseInitChain { + return abci.ResponseInitChain{ + Validators: appVals, + } + }, + } clientCreator := proxy.NewLocalClientCreator(app) config, genesisFile := ResetConfig("handshake_test_") - defer os.RemoveAll(config.RootDir) + t.Cleanup(func() { require.NoError(t, os.RemoveAll(config.RootDir)) }) stateDB, state, store := makeStateAndStore(config, genesisFile, "v0.0.0-test") oldValAddr := state.Validators.Validators[0].Address @@ -1144,13 +1152,9 @@ func TestHandshakeUpdatesValidators(t *testing.T) { genDoc, _ := sm.MakeGenesisDocFromFile(genesisFile) handshaker := NewHandshaker(stateDB, state, store, genDoc) proxyApp := appconn.NewAppConns(clientCreator) - if err := proxyApp.Start(); err != nil { - t.Fatalf("Error starting proxy app connections: %v", err) - } - defer proxyApp.Stop() - if err := handshaker.Handshake(proxyApp); err != nil { - t.Fatalf("Error on abci handshake: %v", err) - } + require.NoError(t, proxyApp.Start(), "Error starting proxy app connections") + t.Cleanup(func() { require.NoError(t, proxyApp.Stop()) }) + require.NoError(t, handshaker.Handshake(proxyApp), "Error on abci handshake") // reload the state, check the validator set was updated state = sm.LoadState(stateDB) @@ -1161,14 +1165,43 @@ func TestHandshakeUpdatesValidators(t *testing.T) { assert.Equal(t, newValAddr, expectValAddr) } -// returns the vals on InitChain +func TestHandshakeGenesisResponseDeliverTx(t *testing.T) { + t.Parallel() + + const numInitResponses = 42 + + app := initChainApp{ + initChain: func(req abci.RequestInitChain) abci.ResponseInitChain { + return abci.ResponseInitChain{ + TxResponses: make([]abci.ResponseDeliverTx, numInitResponses), + } + }, + } + clientCreator := proxy.NewLocalClientCreator(app) + + config, genesisFile := ResetConfig("handshake_test_") + t.Cleanup(func() { require.NoError(t, os.RemoveAll(config.RootDir)) }) + stateDB, state, store := makeStateAndStore(config, genesisFile, "v0.0.0-test") + + // now start the app using the handshake - it should sync + genDoc, _ := sm.MakeGenesisDocFromFile(genesisFile) + handshaker := NewHandshaker(stateDB, state, store, genDoc) + proxyApp := appconn.NewAppConns(clientCreator) + require.NoError(t, proxyApp.Start(), "Error starting proxy app connections") + t.Cleanup(func() { require.NoError(t, proxyApp.Stop()) }) + require.NoError(t, handshaker.Handshake(proxyApp), "Error on abci handshake") + + // check that the genesis transaction results are saved + res, err := sm.LoadABCIResponses(stateDB, 0) + require.NoError(t, err, "Failed to load genesis ABCI responses") + assert.Len(t, res.DeliverTxs, numInitResponses) +} + type initChainApp struct { abci.BaseApplication - vals []abci.ValidatorUpdate + initChain func(req abci.RequestInitChain) abci.ResponseInitChain } -func (ica *initChainApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { - return abci.ResponseInitChain{ - Validators: ica.vals, - } +func (m initChainApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { + return m.initChain(req) } diff --git a/tm2/pkg/bft/consensus/state.go b/tm2/pkg/bft/consensus/state.go index 233a2de65d9..3f71dac368c 100644 --- a/tm2/pkg/bft/consensus/state.go +++ b/tm2/pkg/bft/consensus/state.go @@ -1392,6 +1392,9 @@ func (cs *ConsensusState) finalizeCommit(height int64) { // * cs.Height has been increment to height+1 // * cs.Step is now cstypes.RoundStepNewHeight // * cs.StartTime is set to when we will start round0. + + // Log the telemetry + cs.logTelemetry(block) } // ----------------------------------------------------------------------------- @@ -1766,6 +1769,30 @@ func (cs *ConsensusState) signAddVote(type_ types.SignedMsgType, hash []byte, he return nil } +// logTelemetry logs the consensus state telemetry +func (cs *ConsensusState) logTelemetry(block *types.Block) { + if !telemetry.MetricsEnabled() { + return + } + + // Log the validator telemetry + metrics.ValidatorsCount.Record(context.Background(), int64(cs.Validators.Size())) + metrics.ValidatorsVotingPower.Record(context.Background(), cs.Validators.TotalVotingPower()) + + // Log the block telemetry + if block.Height > 1 { + if lastBlockMeta := cs.blockStore.LoadBlockMeta(block.Height - 1); lastBlockMeta != nil { + metrics.BlockInterval.Record( + context.Background(), + int64(block.Time.Sub(lastBlockMeta.Header.Time).Seconds()), + ) + } + } + + metrics.BlockTxs.Record(context.Background(), block.TotalTxs) + metrics.BlockSizeBytes.Record(context.Background(), int64(block.Size())) +} + // --------------------------------------------------------- func CompareHRS(h1 int64, r1 int, s1 cstypes.RoundStepType, h2 int64, r2 int, s2 cstypes.RoundStepType) int { diff --git a/tm2/pkg/bft/consensus/state_test.go b/tm2/pkg/bft/consensus/state_test.go index 35877837ab3..201cf8906b3 100644 --- a/tm2/pkg/bft/consensus/state_test.go +++ b/tm2/pkg/bft/consensus/state_test.go @@ -76,10 +76,14 @@ func TestStateProposerSelection0(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, newRoundCh) // Wait for new round so proposer is set. ensureNewRound(newRoundCh, height, round) + // Wait for complete proposal. + ensureNewProposal(proposalCh, height, round) + // Commit a block and ensure proposer for the next height is correct. prop := cs1.GetRoundState().Validators.GetProposer() address := cs1.privValidator.GetPubKey().Address() @@ -87,9 +91,6 @@ func TestStateProposerSelection0(t *testing.T) { t.Fatalf("expected proposer to be validator %d. Got %X", 0, prop.Address) } - // Wait for complete proposal. - ensureNewProposal(proposalCh, height, round) - rs := cs1.GetRoundState() signAddVotes(cs1, types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) @@ -121,6 +122,7 @@ func TestStateProposerSelection2(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, newRoundCh) ensureNewRound(newRoundCh, height, round) // wait for the new round @@ -156,7 +158,7 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { cs.Stop() cs.Wait() }() - + defer ensureDrainedChannels(t, timeoutCh) // if we're not a validator, EnterPropose should timeout ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) @@ -183,6 +185,7 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { cs.Stop() cs.Wait() }() + defer ensureDrainedChannels(t, proposalCh, newRoundCh, timeoutCh) // Wait for new round so proposer is set. ensureNewRound(newRoundCh, height, round) @@ -247,6 +250,7 @@ func TestStateBadProposal(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, voteCh) // wait for proposal ensureProposal(proposalCh, height, round, blockID) @@ -285,10 +289,12 @@ func TestStateFullRound1(t *testing.T) { cs.Stop() cs.Wait() }() + defer ensureDrainedChannels(t, newRoundCh, voteCh, propCh) ensureNewRound(newRoundCh, height, round) ensureNewProposal(propCh, height, round) + propBlockHash := cs.GetRoundState().ProposalBlock.Hash() ensurePrevote(voteCh, height, round) // wait for prevote @@ -319,6 +325,7 @@ func TestStateFullRoundNil(t *testing.T) { cs.Stop() cs.Wait() }() + defer ensureDrainedChannels(t, voteCh) ensurePrevote(voteCh, height, round) // prevote ensurePrecommit(voteCh, height, round) // precommit @@ -345,6 +352,7 @@ func TestStateFullRound2(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, voteCh, newBlockCh) ensurePrevote(voteCh, height, round) // prevote @@ -390,6 +398,8 @@ func TestStateLockNoPOL(t *testing.T) { proposalCh := subscribe(cs1.evsw, cstypes.EventCompleteProposal{}) newRoundCh := subscribe(cs1.evsw, cstypes.EventNewRound{}) + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, timeoutProposeCh, newRoundCh, voteCh) + /* Round1 (cs1, B) // B B // B B2 */ @@ -400,18 +410,18 @@ func TestStateLockNoPOL(t *testing.T) { ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) // prevote roundState := cs1.GetRoundState() theBlockHash := roundState.ProposalBlock.Hash() thePartSetHeader := roundState.ProposalBlockParts.Header() - - ensurePrevote(voteCh, height, round) // prevote + validatePrevote(cs1, round, vss[0], theBlockHash) // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: signAddVotes(cs1, types.PrevoteType, theBlockHash, thePartSetHeader, vs2) - ensurePrevote(voteCh, height, round) // prevote - + ensurePrevote(voteCh, height, round) // prevote ensurePrecommit(voteCh, height, round) // precommit + // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) @@ -441,14 +451,13 @@ func TestStateLockNoPOL(t *testing.T) { // now we're on a new round and not the proposer, so wait for timeout ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + // wait to finish prevote + ensurePrevote(voteCh, height, round) rs := cs1.GetRoundState() - if rs.ProposalBlock != nil { panic("Expected proposal block to be nil") } - // wait to finish prevote - ensurePrevote(voteCh, height, round) // we should have prevoted our locked block validatePrevote(cs1, round, vss[0], rs.LockedBlock.Hash()) @@ -481,8 +490,8 @@ func TestStateLockNoPOL(t *testing.T) { */ incrementRound(vs2) - ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) // prevote rs = cs1.GetRoundState() // now we're on a new round and are the proposer @@ -490,9 +499,7 @@ func TestStateLockNoPOL(t *testing.T) { panic(fmt.Sprintf("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) } - ensurePrevote(voteCh, height, round) // prevote validatePrevote(cs1, round, vss[0], rs.LockedBlock.Hash()) - signAddVotes(cs1, types.PrevoteType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) ensurePrevote(voteCh, height, round) @@ -579,14 +586,16 @@ func TestStateLockPOLRelock(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, newRoundCh, newBlockCh, voteCh) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) // prevote rs := cs1.GetRoundState() theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensurePrevote(voteCh, height, round) // prevote + validatePrevote(cs1, round, vss[0], theBlockHash) signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) @@ -676,14 +685,15 @@ func TestStateLockPOLUnlock(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, newRoundCh, voteCh, unlockCh) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) rs := cs1.GetRoundState() theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensurePrevote(voteCh, height, round) validatePrevote(cs1, round, vss[0], theBlockHash) signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) @@ -770,13 +780,14 @@ func TestStateLockPOLSafety1(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, timeoutProposeCh, newRoundCh, voteCh) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) rs := cs1.GetRoundState() propBlock := rs.ProposalBlock - ensurePrevote(voteCh, height, round) validatePrevote(cs1, round, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it @@ -811,17 +822,15 @@ func TestStateLockPOLSafety1(t *testing.T) { // a polka happened but we didn't see it! */ + // go to prevote, prevote for proposal block ensureNewProposal(proposalCh, height, round) - + ensurePrevote(voteCh, height, round) rs = cs1.GetRoundState() - if rs.LockedBlock != nil { panic("we should not be locked!") } t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash)) - // go to prevote, prevote for proposal block - ensurePrevote(voteCh, height, round) validatePrevote(cs1, round, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it @@ -854,6 +863,7 @@ func TestStateLockPOLSafety1(t *testing.T) { validatePrevote(cs1, round, vss[0], propBlockHash) newStepCh := subscribe(cs1.evsw, cstypes.EventNewRoundStep{}) + defer ensureDrainedChannels(t, newStepCh) // before prevotes from the previous round are added // add prevotes from the earlier round @@ -912,6 +922,7 @@ func TestStateLockPOLSafety2(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, newRoundCh, unlockCh, voteCh) ensureNewRound(newRoundCh, height, round) if err := cs1.SetProposalAndBlock(prop1, propBlock1, propBlockParts1, "some peer"); err != nil { @@ -992,13 +1003,15 @@ func TestProposeValidBlock(t *testing.T) { cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, timeoutProposeCh, newRoundCh, unlockCh, voteCh) + ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) rs := cs1.GetRoundState() propBlock := rs.ProposalBlock propBlockHash := propBlock.Hash() - ensurePrevote(voteCh, height, round) validatePrevote(cs1, round, vss[0], propBlockHash) // the others sign a polka @@ -1055,7 +1068,7 @@ func TestProposeValidBlock(t *testing.T) { t.Log("### ONTO ROUND 4") ensureNewProposal(proposalCh, height, round) - + ensurePrevote(voteCh, height, round) rs = cs1.GetRoundState() assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), propBlockHash)) assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), rs.ValidBlock.Hash())) @@ -1087,15 +1100,16 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, timeoutWaitCh, newRoundCh, validBlockCh, voteCh, proposalCh) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) rs := cs1.GetRoundState() propBlock := rs.ProposalBlock propBlockHash := propBlock.Hash() propBlockParts := propBlock.MakePartSet(partSize) - ensurePrevote(voteCh, height, round) validatePrevote(cs1, round, vss[0], propBlockHash) // vs2 send prevote for propBlock @@ -1156,6 +1170,7 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, timeoutWaitCh, timeoutProposeCh, newRoundCh, validBlockCh, voteCh, proposalCh) ensureNewRound(newRoundCh, height, round) ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) @@ -1207,6 +1222,7 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, timeoutWaitCh, newRoundCh) ensureNewRound(newRoundCh, height, round) signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) @@ -1236,6 +1252,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, timeoutWaitCh, newRoundCh, voteCh) ensureNewRound(newRoundCh, height, round) ensurePrevote(voteCh, height, round) @@ -1276,6 +1293,7 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, timeoutWaitCh, newRoundCh, voteCh) ensureNewRound(newRoundCh, height, round) ensurePrevote(voteCh, height, round) @@ -1316,6 +1334,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, timeoutProposeCh, newRoundCh, voteCh) ensureNewRound(newRoundCh, height, round) incrementRound(vss[1:]...) @@ -1353,6 +1372,7 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, newRoundCh, newRoundCh, validBlockCh) ensureNewRound(newRoundCh, height, round) // vs2, vs3 and vs4 send precommit for propBlock @@ -1391,6 +1411,7 @@ func TestCommitFromPreviousRound(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, newRoundCh, validBlockCh) ensureNewRound(newRoundCh, height, round) // vs2, vs3 and vs4 send precommit for propBlock for the previous round @@ -1446,14 +1467,15 @@ func TestStartNextHeightCorrectly(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, newRoundCh, voteCh, newBlockHeader) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) + ensurePrevote(voteCh, height, round) rs := cs1.GetRoundState() theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensurePrevote(voteCh, height, round) validatePrevote(cs1, round, vss[0], theBlockHash) signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) @@ -1478,6 +1500,7 @@ func TestStartNextHeightCorrectly(t *testing.T) { height, round = height+1, 0 ensureNewRound(newRoundCh, height, round) ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensurePrevote(voteCh, height, round) rs = cs1.GetRoundState() assert.False(t, rs.TriggeredTimeoutPrecommit, "triggeredTimeoutPrecommit should be false at the beginning of each round") } @@ -1507,6 +1530,7 @@ func TestFlappyResetTimeoutPrecommitUponNewHeight(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, newRoundCh, voteCh, newBlockHeader) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) @@ -1653,6 +1677,7 @@ func TestFlappyStateHalt1(t *testing.T) { cs1.Stop() cs1.Wait() }() + defer ensureDrainedChannels(t, proposalCh, timeoutWaitCh, newRoundCh, voteCh, newBlockCh) ensureNewRound(newRoundCh, height, round) ensureNewProposal(proposalCh, height, round) diff --git a/tm2/pkg/bft/consensus/ticker.go b/tm2/pkg/bft/consensus/ticker.go index 8448e014260..461a93e3e6e 100644 --- a/tm2/pkg/bft/consensus/ticker.go +++ b/tm2/pkg/bft/consensus/ticker.go @@ -67,8 +67,13 @@ func (t *timeoutTicker) Chan() <-chan timeoutInfo { // ScheduleTimeout schedules a new timeout by sending on the internal tickChan. // The timeoutRoutine is always available to read from tickChan, so this won't block. // The scheduling may fail if the timeoutRoutine has already scheduled a timeout for a later height/round/step. +// If the service has been closed, the timeout will be ignored. func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) { - t.tickChan <- ti + select { + case t.tickChan <- ti: + case <-t.Quit(): + t.Logger.Warn("Unable to schedule timeout as service has been closed") + } } // ------------------------------------------------------------- diff --git a/tm2/pkg/bft/mempool/clist_mempool.go b/tm2/pkg/bft/mempool/clist_mempool.go index 38c24866892..2cad23c68e7 100644 --- a/tm2/pkg/bft/mempool/clist_mempool.go +++ b/tm2/pkg/bft/mempool/clist_mempool.go @@ -3,6 +3,7 @@ package mempool import ( "bytes" "container/list" + "context" "crypto/sha256" "fmt" "log/slog" @@ -19,6 +20,8 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/log" osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/telemetry" + "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" ) // -------------------------------------------------------------------------------- @@ -333,6 +336,22 @@ func (mem *CListMempool) addTx(memTx *mempoolTx) { e := mem.txs.PushBack(memTx) mem.txsMap.Store(txKey(memTx.tx), e) atomic.AddInt64(&mem.txsBytes, int64(len(memTx.tx))) + + // Update the telemetry + mem.logTelemetry() +} + +// logTelemetry logs the mempool telemetry +func (mem *CListMempool) logTelemetry() { + if !telemetry.MetricsEnabled() { + return + } + + // Log the total number of mempool transactions + metrics.NumMempoolTxs.Record(context.Background(), int64(mem.txs.Len())) + + // Log the total number of the mempool cache transactions + metrics.NumCachedTxs.Record(context.Background(), int64(mem.cache.Len())) } // Called from: @@ -347,6 +366,9 @@ func (mem *CListMempool) removeTx(tx types.Tx, elem *clist.CElement, removeFromC if removeFromCache { mem.cache.Remove(tx) } + + // Update the telemetry + mem.logTelemetry() } // callback, which is called after the app checked the tx for the first time. @@ -622,12 +644,13 @@ type txCache interface { Reset() Push(tx types.Tx) bool Remove(tx types.Tx) + Len() int } // mapTxCache maintains a LRU cache of transactions. This only stores the hash // of the tx, due to memory concerns. type mapTxCache struct { - mtx sync.Mutex + mtx sync.RWMutex size int map_ map[[sha256.Size]byte]*list.Element list *list.List @@ -652,6 +675,13 @@ func (cache *mapTxCache) Reset() { cache.mtx.Unlock() } +func (cache *mapTxCache) Len() int { + cache.mtx.RLock() + defer cache.mtx.RUnlock() + + return cache.list.Len() +} + // Push adds the given tx to the cache and returns true. It returns // false if tx is already in the cache. func (cache *mapTxCache) Push(tx types.Tx) bool { @@ -698,6 +728,7 @@ var _ txCache = (*nopTxCache)(nil) func (nopTxCache) Reset() {} func (nopTxCache) Push(types.Tx) bool { return true } func (nopTxCache) Remove(types.Tx) {} +func (nopTxCache) Len() int { return 0 } // -------------------------------------------------------------------------------- diff --git a/tm2/pkg/bft/mempool/config/config.go b/tm2/pkg/bft/mempool/config/config.go index 198b34291bc..47df01238e5 100644 --- a/tm2/pkg/bft/mempool/config/config.go +++ b/tm2/pkg/bft/mempool/config/config.go @@ -7,13 +7,13 @@ import "github.com/gnolang/gno/tm2/pkg/errors" // MempoolConfig defines the configuration options for the Tendermint mempool type MempoolConfig struct { - RootDir string `toml:"home"` - Recheck bool `toml:"recheck"` - Broadcast bool `toml:"broadcast"` - WalPath string `toml:"wal_dir"` - Size int `toml:"size" comment:"Maximum number of transactions in the mempool"` - MaxPendingTxsBytes int64 `toml:"max_pending_txs_bytes" comment:"Limit the total size of all txs in the mempool.\n This only accounts for raw transactions (e.g. given 1MB transactions and\n max_txs_bytes=5MB, mempool will only accept 5 transactions)."` - CacheSize int `toml:"cache_size" comment:"Size of the cache (used to filter transactions we saw earlier) in transactions"` + RootDir string `json:"home" toml:"home"` + Recheck bool `json:"recheck" toml:"recheck"` + Broadcast bool `json:"broadcast" toml:"broadcast"` + WalPath string `json:"wal_dir" toml:"wal_dir"` + Size int `json:"size" toml:"size" comment:"Maximum number of transactions in the mempool"` + MaxPendingTxsBytes int64 `json:"max_pending_txs_bytes" toml:"max_pending_txs_bytes" comment:"Limit the total size of all txs in the mempool.\n This only accounts for raw transactions (e.g. given 1MB transactions and\n max_txs_bytes=5MB, mempool will only accept 5 transactions)."` + CacheSize int `json:"cache_size" toml:"cache_size" comment:"Size of the cache (used to filter transactions we saw earlier) in transactions"` } // DefaultMempoolConfig returns a default configuration for the Tendermint mempool diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 9dd63464310..e29de3dd1ae 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -80,7 +80,12 @@ type NodeProvider func(*cfg.Config, *slog.Logger) (*Node, error) // DefaultNewNode returns a Tendermint node with default settings for the // PrivValidator, ClientCreator, GenesisDoc, and DBProvider. // It implements NodeProvider. -func DefaultNewNode(config *cfg.Config, genesisFile string, logger *slog.Logger) (*Node, error) { +func DefaultNewNode( + config *cfg.Config, + genesisFile string, + evsw events.EventSwitch, + logger *slog.Logger, +) (*Node, error) { // Generate node PrivKey nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { @@ -105,6 +110,7 @@ func DefaultNewNode(config *cfg.Config, genesisFile string, logger *slog.Logger) appClientCreator, DefaultGenesisDocProviderFunc(genesisFile), DefaultDBProvider, + evsw, logger, ) } @@ -415,6 +421,7 @@ func NewNode(config *cfg.Config, clientCreator appconn.ClientCreator, genesisDocProvider GenesisDocProvider, dbProvider DBProvider, + evsw events.EventSwitch, logger *slog.Logger, options ...Option, ) (*Node, error) { @@ -434,12 +441,6 @@ func NewNode(config *cfg.Config, return nil, err } - // EventSwitch and EventStoreService must be started before the handshake because - // we might need to store the txs of the replayed block as this might not have happened - // when the node stopped last time (i.e. the node stopped after it saved the block - // but before it indexed the txs, or, endblocker panicked) - evsw := events.NewEventSwitch() - // Signal readiness when receiving the first block. const readinessListenerID = "first_block_listener" diff --git a/tm2/pkg/bft/node/node_test.go b/tm2/pkg/bft/node/node_test.go index 28ab95e0064..e28464ff711 100644 --- a/tm2/pkg/bft/node/node_test.go +++ b/tm2/pkg/bft/node/node_test.go @@ -35,7 +35,7 @@ func TestNodeStartStop(t *testing.T) { defer os.RemoveAll(config.RootDir) // create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewNoopLogger()) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewNoopLogger()) require.NoError(t, err) err = n.Start() require.NoError(t, err) @@ -98,7 +98,7 @@ func TestNodeDelayedStart(t *testing.T) { now := tmtime.Now() // create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) n.GenesisDoc().GenesisTime = now.Add(2 * time.Second) require.NoError(t, err) @@ -115,7 +115,7 @@ func TestNodeReady(t *testing.T) { defer os.RemoveAll(config.RootDir) // Create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) // Assert that blockstore has zero block before waiting for the first block @@ -148,7 +148,7 @@ func TestNodeSetAppVersion(t *testing.T) { defer os.RemoveAll(config.RootDir) // create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) // default config uses the kvstore app @@ -192,7 +192,7 @@ func TestNodeSetPrivValTCP(t *testing.T) { }() defer signerServer.Stop() - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) assert.IsType(t, &privval.SignerClient{}, n.PrivValidator()) } @@ -205,7 +205,7 @@ func TestPrivValidatorListenAddrNoProtocol(t *testing.T) { defer os.RemoveAll(config.RootDir) config.BaseConfig.PrivValidatorListenAddr = addrNoPrefix - _, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + _, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) assert.Error(t, err) } @@ -236,7 +236,7 @@ func TestNodeSetPrivValIPC(t *testing.T) { }() defer pvsc.Stop() - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) assert.IsType(t, &privval.SignerClient{}, n.PrivValidator()) } @@ -324,6 +324,7 @@ func TestNodeNewNodeCustomReactors(t *testing.T) { proxy.DefaultClientCreator(nil, config.ProxyApp, config.ABCI, config.DBDir()), DefaultGenesisDocProviderFunc(genesisFile), DefaultDBProvider, + events.NewEventSwitch(), log.NewTestingLogger(t), CustomReactors(map[string]p2p.Reactor{"FOO": cr, "BLOCKCHAIN": customBlockchainReactor}), ) diff --git a/tm2/pkg/bft/privval/file.go b/tm2/pkg/bft/privval/file.go index b1bac8416f7..7ed586b7c05 100644 --- a/tm2/pkg/bft/privval/file.go +++ b/tm2/pkg/bft/privval/file.go @@ -38,10 +38,11 @@ func voteToStep(vote *types.Vote) int8 { // ------------------------------------------------------------------------------- // FilePVKey stores the immutable part of PrivValidator. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go type FilePVKey struct { - Address types.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - PrivKey crypto.PrivKey `json:"priv_key"` + Address types.Address `json:"address" comment:"the validator address"` + PubKey crypto.PubKey `json:"pub_key" comment:"the validator public key"` + PrivKey crypto.PrivKey `json:"priv_key" comment:"the validator private key"` filePath string } @@ -66,12 +67,13 @@ func (pvKey FilePVKey) Save() { // ------------------------------------------------------------------------------- // FilePVLastSignState stores the mutable part of PrivValidator. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go type FilePVLastSignState struct { - Height int64 `json:"height"` - Round int `json:"round"` - Step int8 `json:"step"` - Signature []byte `json:"signature,omitempty"` - SignBytes []byte `json:"signbytes,omitempty"` + Height int64 `json:"height" comment:"the height of the last sign"` + Round int `json:"round" comment:"the round of the last sign"` + Step int8 `json:"step" comment:"the step of the last sign"` + Signature []byte `json:"signature,omitempty" comment:"the signature of the last sign"` + SignBytes []byte `json:"signbytes,omitempty" comment:"the raw signature bytes of the last sign"` filePath string } diff --git a/tm2/pkg/bft/rpc/config/config.go b/tm2/pkg/bft/rpc/config/config.go index 1428861626c..fe527450178 100644 --- a/tm2/pkg/bft/rpc/config/config.go +++ b/tm2/pkg/bft/rpc/config/config.go @@ -16,36 +16,36 @@ const ( // RPCConfig defines the configuration options for the Tendermint RPC server type RPCConfig struct { - RootDir string `toml:"home"` + RootDir string `json:"home" toml:"home"` // TCP or UNIX socket address for the RPC server to listen on - ListenAddress string `toml:"laddr" comment:"TCP or UNIX socket address for the RPC server to listen on"` + ListenAddress string `json:"laddr" toml:"laddr" comment:"TCP or UNIX socket address for the RPC server to listen on"` // A list of origins a cross-domain request can be executed from. // If the special '*' value is present in the list, all origins will be allowed. // An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). // Only one wildcard can be used per origin. - CORSAllowedOrigins []string `toml:"cors_allowed_origins" comment:"A list of origins a cross-domain request can be executed from\n Default value '[]' disables cors support\n Use '[\"*\"]' to allow any origin"` + CORSAllowedOrigins []string `json:"cors_allowed_origins" toml:"cors_allowed_origins" comment:"A list of origins a cross-domain request can be executed from\n Default value '[]' disables cors support\n Use '[\"*\"]' to allow any origin"` // A list of methods the client is allowed to use with cross-domain requests. - CORSAllowedMethods []string `toml:"cors_allowed_methods" comment:"A list of methods the client is allowed to use with cross-domain requests"` + CORSAllowedMethods []string `json:"cors_allowed_methods" toml:"cors_allowed_methods" comment:"A list of methods the client is allowed to use with cross-domain requests"` // A list of non simple headers the client is allowed to use with cross-domain requests. - CORSAllowedHeaders []string `toml:"cors_allowed_headers" comment:"A list of non simple headers the client is allowed to use with cross-domain requests"` + CORSAllowedHeaders []string `json:"cors_allowed_headers" toml:"cors_allowed_headers" comment:"A list of non simple headers the client is allowed to use with cross-domain requests"` // TCP or UNIX socket address for the gRPC server to listen on // NOTE: This server only supports /broadcast_tx_commit - GRPCListenAddress string `toml:"grpc_laddr" comment:"TCP or UNIX socket address for the gRPC server to listen on\n NOTE: This server only supports /broadcast_tx_commit"` + GRPCListenAddress string `json:"grpc_laddr" toml:"grpc_laddr" comment:"TCP or UNIX socket address for the gRPC server to listen on\n NOTE: This server only supports /broadcast_tx_commit"` // Maximum number of simultaneous connections. // Does not include RPC (HTTP&WebSocket) connections. See max_open_connections // If you want to accept a larger number than the default, make sure // you increase your OS limits. // 0 - unlimited. - GRPCMaxOpenConnections int `toml:"grpc_max_open_connections" comment:"Maximum number of simultaneous connections.\n Does not include RPC (HTTP&WebSocket) connections. See max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` + GRPCMaxOpenConnections int `json:"grpc_max_open_connections" toml:"grpc_max_open_connections" comment:"Maximum number of simultaneous connections.\n Does not include RPC (HTTP&WebSocket) connections. See max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` // Activate unsafe RPC commands like /dial_persistent_peers and /unsafe_flush_mempool - Unsafe bool `toml:"unsafe" comment:"Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool"` + Unsafe bool `json:"unsafe" toml:"unsafe" comment:"Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool"` // Maximum number of simultaneous connections (including WebSocket). // Does not include gRPC connections. See grpc_max_open_connections @@ -54,19 +54,19 @@ type RPCConfig struct { // 0 - unlimited. // Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} // 1024 - 40 - 10 - 50 = 924 = ~900 - MaxOpenConnections int `toml:"max_open_connections" comment:"Maximum number of simultaneous connections (including WebSocket).\n Does not include gRPC connections. See grpc_max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` + MaxOpenConnections int `json:"max_open_connections" toml:"max_open_connections" comment:"Maximum number of simultaneous connections (including WebSocket).\n Does not include gRPC connections. See grpc_max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` // How long to wait for a tx to be committed during /broadcast_tx_commit // WARNING: Using a value larger than 10s will result in increasing the // global HTTP write timeout, which applies to all connections and endpoints. // See https://github.com/gnolang/gno/tm2/pkg/bft/issues/3435 - TimeoutBroadcastTxCommit time.Duration `toml:"timeout_broadcast_tx_commit" comment:"How long to wait for a tx to be committed during /broadcast_tx_commit.\n WARNING: Using a value larger than 10s will result in increasing the\n global HTTP write timeout, which applies to all connections and endpoints.\n See https://github.com/tendermint/classic/issues/3435"` + TimeoutBroadcastTxCommit time.Duration `json:"timeout_broadcast_tx_commit" toml:"timeout_broadcast_tx_commit" comment:"How long to wait for a tx to be committed during /broadcast_tx_commit.\n WARNING: Using a value larger than 10s will result in increasing the\n global HTTP write timeout, which applies to all connections and endpoints.\n See https://github.com/tendermint/classic/issues/3435"` // Maximum size of request body, in bytes - MaxBodyBytes int64 `toml:"max_body_bytes" comment:"Maximum size of request body, in bytes"` + MaxBodyBytes int64 `json:"max_body_bytes" toml:"max_body_bytes" comment:"Maximum size of request body, in bytes"` // Maximum size of request header, in bytes - MaxHeaderBytes int `toml:"max_header_bytes" comment:"Maximum size of request header, in bytes"` + MaxHeaderBytes int `json:"max_header_bytes" toml:"max_header_bytes" comment:"Maximum size of request header, in bytes"` // The path to a file containing certificate that is used to create the HTTPS server. // Might be either absolute path or path related to tendermint's config directory. @@ -76,13 +76,13 @@ type RPCConfig struct { // and the CA's certificate. // // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. - TLSCertFile string `toml:"tls_cert_file" comment:"The path to a file containing certificate that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n If the certificate is signed by a certificate authority,\n the certFile should be the concatenation of the server's certificate, any intermediates,\n and the CA's certificate.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` + TLSCertFile string `json:"tls_cert_file" toml:"tls_cert_file" comment:"The path to a file containing certificate that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n If the certificate is signed by a certificate authority,\n the certFile should be the concatenation of the server's certificate, any intermediates,\n and the CA's certificate.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` // The path to a file containing matching private key that is used to create the HTTPS server. // Might be either absolute path or path related to tendermint's config directory. // // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. - TLSKeyFile string `toml:"tls_key_file" comment:"The path to a file containing matching private key that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` + TLSKeyFile string `json:"tls_key_file" toml:"tls_key_file" comment:"The path to a file containing matching private key that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` } // DefaultRPCConfig returns a default configuration for the RPC server @@ -111,8 +111,8 @@ func DefaultRPCConfig() *RPCConfig { // TestRPCConfig returns a configuration for testing the RPC server func TestRPCConfig() *RPCConfig { cfg := DefaultRPCConfig() - cfg.ListenAddress = "tcp://0.0.0.0:36657" - cfg.GRPCListenAddress = "tcp://0.0.0.0:36658" + cfg.ListenAddress = "tcp://0.0.0.0:26657" + cfg.GRPCListenAddress = "tcp://0.0.0.0:26658" cfg.Unsafe = true return cfg } diff --git a/tm2/pkg/bft/rpc/core/blocks.go b/tm2/pkg/bft/rpc/core/blocks.go index 06bb3de1174..53ed25ade11 100644 --- a/tm2/pkg/bft/rpc/core/blocks.go +++ b/tm2/pkg/bft/rpc/core/blocks.go @@ -400,7 +400,7 @@ func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, erro // ``` func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) { storeHeight := blockStore.Height() - height, err := getHeight(storeHeight, heightPtr) + height, err := getHeightWithMin(storeHeight, heightPtr, 0) if err != nil { return nil, err } @@ -418,10 +418,14 @@ func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockR } func getHeight(currentHeight int64, heightPtr *int64) (int64, error) { + return getHeightWithMin(currentHeight, heightPtr, 1) +} + +func getHeightWithMin(currentHeight int64, heightPtr *int64, min int64) (int64, error) { if heightPtr != nil { height := *heightPtr - if height <= 0 { - return 0, fmt.Errorf("height must be greater than 0") + if height < min { + return 0, fmt.Errorf("height must be greater than or equal to %d", min) } if height > currentHeight { return 0, fmt.Errorf("height must be less than or equal to the current blockchain height") diff --git a/tm2/pkg/bft/rpc/core/blocks_test.go b/tm2/pkg/bft/rpc/core/blocks_test.go index 0fcd40f6d14..550cc1542c9 100644 --- a/tm2/pkg/bft/rpc/core/blocks_test.go +++ b/tm2/pkg/bft/rpc/core/blocks_test.go @@ -55,3 +55,40 @@ func TestBlockchainInfo(t *testing.T) { } } } + +func TestGetHeight(t *testing.T) { + t.Parallel() + + cases := []struct { + currentHeight int64 + heightPtr *int64 + min int64 + res int64 + wantErr bool + }{ + // height >= min + {42, int64Ptr(0), 0, 0, false}, + {42, int64Ptr(1), 0, 1, false}, + + // height < min + {42, int64Ptr(0), 1, 0, true}, + + // nil height + {42, nil, 1, 42, false}, + } + + for i, c := range cases { + caseString := fmt.Sprintf("test %d failed", i) + res, err := getHeightWithMin(c.currentHeight, c.heightPtr, c.min) + if c.wantErr { + require.Error(t, err, caseString) + } else { + require.NoError(t, err, caseString) + require.Equal(t, res, c.res, caseString) + } + } +} + +func int64Ptr(v int64) *int64 { + return &v +} diff --git a/tm2/pkg/bft/rpc/core/status.go b/tm2/pkg/bft/rpc/core/status.go index 9bc5f3ea6fc..950bac7ddf7 100644 --- a/tm2/pkg/bft/rpc/core/status.go +++ b/tm2/pkg/bft/rpc/core/status.go @@ -125,7 +125,7 @@ func validatorAtHeight(h int64) *types.Validator { lastBlockHeight, vals := consensusState.GetValidators() if lastBlockHeight == h { for _, val := range vals { - if val.Address != privValAddress { + if val.Address == privValAddress { return val } } diff --git a/tm2/pkg/bft/rpc/lib/client/http/client.go b/tm2/pkg/bft/rpc/lib/client/http/client.go index 34d301deba2..aa4fc5c5392 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client.go @@ -17,9 +17,10 @@ import ( const ( protoHTTP = "http" protoHTTPS = "https" - protoWSS = "wss" - protoWS = "ws" protoTCP = "tcp" + + portHTTP = "80" + portHTTPS = "443" ) var ( @@ -59,7 +60,7 @@ func (c *Client) SendRequest(ctx context.Context, request types.RPCRequest) (*ty } // Make sure the ID matches - if response.ID != response.ID { + if request.ID != response.ID { return nil, ErrRequestResponseIDMismatch } @@ -173,12 +174,7 @@ func defaultHTTPClient(remoteAddr string) *http.Client { } func makeHTTPDialer(remoteAddr string) func(string, string) (net.Conn, error) { - protocol, address, err := parseRemoteAddr(remoteAddr) - if err != nil { - return func(_ string, _ string) (net.Conn, error) { - return nil, err - } - } + protocol, address := parseRemoteAddr(remoteAddr) // net.Dial doesn't understand http/https, so change it to TCP switch protocol { @@ -193,17 +189,14 @@ func makeHTTPDialer(remoteAddr string) func(string, string) (net.Conn, error) { // protocol - client's protocol (for example, "http", "https", "wss", "ws", "tcp") // trimmedS - rest of the address (for example, "192.0.2.1:25", "[2001:db8::1]:80") with "/" replaced with "." -func toClientAddrAndParse(remoteAddr string) (string, string, error) { - protocol, address, err := parseRemoteAddr(remoteAddr) - if err != nil { - return "", "", err - } +func toClientAddrAndParse(remoteAddr string) (string, string) { + protocol, address := parseRemoteAddr(remoteAddr) // protocol to use for http operations, to support both http and https var clientProtocol string // default to http for unknown protocols (ex. tcp) switch protocol { - case protoHTTP, protoHTTPS, protoWS, protoWSS: + case protoHTTP, protoHTTPS: clientProtocol = protocol default: clientProtocol = protoHTTP @@ -212,14 +205,11 @@ func toClientAddrAndParse(remoteAddr string) (string, string, error) { // replace / with . for http requests (kvstore domain) trimmedAddress := strings.Replace(address, "/", ".", -1) - return clientProtocol, trimmedAddress, nil + return clientProtocol, trimmedAddress } func toClientAddress(remoteAddr string) (string, error) { - clientProtocol, trimmedAddress, err := toClientAddrAndParse(remoteAddr) - if err != nil { - return "", err - } + clientProtocol, trimmedAddress := toClientAddrAndParse(remoteAddr) return clientProtocol + "://" + trimmedAddress, nil } @@ -227,8 +217,9 @@ func toClientAddress(remoteAddr string) (string, error) { // network - name of the network (for example, "tcp", "unix") // s - rest of the address (for example, "192.0.2.1:25", "[2001:db8::1]:80") // TODO: Deprecate support for IP:PORT or /path/to/socket -func parseRemoteAddr(remoteAddr string) (network string, s string, err error) { +func parseRemoteAddr(remoteAddr string) (string, string) { parts := strings.SplitN(remoteAddr, "://", 2) + var protocol, address string switch len(parts) { case 1: @@ -237,7 +228,19 @@ func parseRemoteAddr(remoteAddr string) (network string, s string, err error) { case 2: protocol, address = parts[0], parts[1] } - return protocol, address, nil + + // Append default ports if not specified + if !strings.Contains(address, ":") { + switch protocol { + case protoHTTPS: + address += ":" + portHTTPS + case protoHTTP, protoTCP: + address += ":" + portHTTP + default: // noop + } + } + + return protocol, address } // isOKStatus returns a boolean indicating if the response diff --git a/tm2/pkg/bft/rpc/lib/client/http/client_test.go b/tm2/pkg/bft/rpc/lib/client/http/client_test.go index 7c4b1e52ac5..4ccbfdc2d1e 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client_test.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client_test.go @@ -17,24 +17,39 @@ func TestClient_parseRemoteAddr(t *testing.T) { t.Parallel() testTable := []struct { - remoteAddr string - network string - rest string + remoteAddr string + expectedNetwork string + expectedRest string }{ { "127.0.0.1", "tcp", - "127.0.0.1", + "127.0.0.1:80", + }, + { + "127.0.0.1:5000", + "tcp", + "127.0.0.1:5000", + }, + { + "http://example.com", + "http", + "example.com:80", }, { "https://example.com", "https", - "example.com", + "example.com:443", + }, + { + "http://example.com:5000", + "http", + "example.com:5000", }, { - "wss://[::1]", - "wss", - "[::1]", + "https://example.com:5000", + "https", + "example.com:5000", }, } @@ -44,11 +59,10 @@ func TestClient_parseRemoteAddr(t *testing.T) { t.Run(testCase.remoteAddr, func(t *testing.T) { t.Parallel() - n, r, err := parseRemoteAddr(testCase.remoteAddr) - require.NoError(t, err) + n, r := parseRemoteAddr(testCase.remoteAddr) - assert.Equal(t, n, testCase.network) - assert.Equal(t, r, testCase.rest) + assert.Equal(t, testCase.expectedNetwork, n) + assert.Equal(t, testCase.expectedRest, r) }) } } @@ -66,7 +80,6 @@ func TestClient_makeHTTPDialer(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "dial tcp:", "should convert https to tcp") - assert.Contains(t, err.Error(), "address .:", "should have parsed the address (as incorrect)") }) t.Run("udp", func(t *testing.T) { @@ -76,7 +89,6 @@ func TestClient_makeHTTPDialer(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "dial udp:", "udp protocol should remain the same") - assert.Contains(t, err.Error(), "address .:", "should have parsed the address (as incorrect)") }) } @@ -96,53 +108,101 @@ func createTestServer( func TestClient_SendRequest(t *testing.T) { t.Parallel() - var ( - request = types.RPCRequest{ - JSONRPC: "2.0", - ID: types.JSONRPCStringID("id"), - } + t.Run("valid request, response", func(t *testing.T) { + t.Parallel() - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - require.Equal(t, "application/json", r.Header.Get("content-type")) + var ( + request = types.RPCRequest{ + JSONRPC: "2.0", + ID: types.JSONRPCStringID("id"), + } - // Parse the message - var req types.RPCRequest - require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) - require.Equal(t, request.ID.String(), req.ID.String()) + handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "application/json", r.Header.Get("content-type")) - // Send an empty response back - response := types.RPCResponse{ + // Parse the message + var req types.RPCRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + require.Equal(t, request.ID.String(), req.ID.String()) + + // Send an empty response back + response := types.RPCResponse{ + JSONRPC: "2.0", + ID: req.ID, + } + + // Marshal the response + marshalledResponse, err := json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(marshalledResponse) + require.NoError(t, err) + }) + + server = createTestServer(t, handler) + ) + + // Create the client + c, err := NewClient(server.URL) + require.NoError(t, err) + + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFn() + + // Send the request + resp, err := c.SendRequest(ctx, request) + require.NoError(t, err) + + assert.Equal(t, request.ID, resp.ID) + assert.Equal(t, request.JSONRPC, resp.JSONRPC) + assert.Nil(t, resp.Result) + assert.Nil(t, resp.Error) + }) + + t.Run("response ID mismatch", func(t *testing.T) { + t.Parallel() + + var ( + request = types.RPCRequest{ JSONRPC: "2.0", - ID: req.ID, + ID: types.JSONRPCStringID("id"), } - // Marshal the response - marshalledResponse, err := json.Marshal(response) - require.NoError(t, err) + handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "application/json", r.Header.Get("content-type")) - _, err = w.Write(marshalledResponse) - require.NoError(t, err) - }) + // Send an empty response back, + // with an invalid ID + response := types.RPCResponse{ + JSONRPC: "2.0", + ID: types.JSONRPCStringID("totally random ID"), + } - server = createTestServer(t, handler) - ) + // Marshal the response + marshalledResponse, err := json.Marshal(response) + require.NoError(t, err) - // Create the client - c, err := NewClient(server.URL) - require.NoError(t, err) + _, err = w.Write(marshalledResponse) + require.NoError(t, err) + }) - ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5) - defer cancelFn() + server = createTestServer(t, handler) + ) - // Send the request - resp, err := c.SendRequest(ctx, request) - require.NoError(t, err) + // Create the client + c, err := NewClient(server.URL) + require.NoError(t, err) + + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFn() - assert.Equal(t, request.ID, resp.ID) - assert.Equal(t, request.JSONRPC, resp.JSONRPC) - assert.Nil(t, resp.Result) - assert.Nil(t, resp.Error) + // Send the request + resp, err := c.SendRequest(ctx, request) + assert.Nil(t, resp) + assert.ErrorIs(t, err, ErrRequestResponseIDMismatch) + }) } func TestClient_SendBatchRequest(t *testing.T) { diff --git a/tm2/pkg/bft/rpc/lib/server/handlers.go b/tm2/pkg/bft/rpc/lib/server/handlers.go index 417f417ba26..88ee26da4a9 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers.go @@ -16,6 +16,8 @@ import ( "strings" "time" + "github.com/gnolang/gno/tm2/pkg/telemetry" + "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" "github.com/gorilla/websocket" "github.com/gnolang/gno/tm2/pkg/amino" @@ -28,6 +30,29 @@ import ( // RegisterRPCFuncs adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions. // "result" is the interface on which the result objects are registered, and is populated with every RPCResponse func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger *slog.Logger) { + // Check if metrics are enabled + if telemetry.MetricsEnabled() { + // HTTP endpoints + for funcName, rpcFunc := range funcMap { + mux.HandleFunc( + "/"+funcName, + telemetryMiddleware( + makeHTTPHandler(rpcFunc, logger), + ), + ) + } + + // JSONRPC endpoints + mux.HandleFunc( + "/", + telemetryMiddleware( + handleInvalidJSONRPCPaths(makeJSONRPCHandler(funcMap, logger)), + ), + ) + + return + } + // HTTP endpoints for funcName, rpcFunc := range funcMap { mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc, logger)) @@ -185,6 +210,21 @@ func handleInvalidJSONRPCPaths(next http.HandlerFunc) http.HandlerFunc { } } +// telemetryMiddleware is the telemetry middleware handler +func telemetryMiddleware(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + next.ServeHTTP(w, r) + + // Log the response time + metrics.HTTPRequestTime.Record( + context.Background(), + time.Since(start).Milliseconds(), + ) + } +} + func mapParamsToArgs(rpcFunc *RPCFunc, params map[string]json.RawMessage, argsOffset int) ([]reflect.Value, error) { values := make([]reflect.Value, len(rpcFunc.argNames)) for i, argName := range rpcFunc.argNames { @@ -258,7 +298,7 @@ func jsonParamsToArgs(rpcFunc *RPCFunc, raw []byte) ([]reflect.Value, error) { // rpc.http // convert from a function name to the http handler -func makeHTTPHandler(rpcFunc *RPCFunc, logger *slog.Logger) func(http.ResponseWriter, *http.Request) { +func makeHTTPHandler(rpcFunc *RPCFunc, logger *slog.Logger) http.HandlerFunc { // Exception for websocket endpoints if rpcFunc.ws { return func(w http.ResponseWriter, r *http.Request) { @@ -626,6 +666,8 @@ func (wsc *wsConnection) readRoutine() { return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)) }) + telemetryEnabled := telemetry.MetricsEnabled() + for { select { case <-wsc.Quit(): @@ -647,6 +689,9 @@ func (wsc *wsConnection) readRoutine() { return } + // Log the request response start time + responseStart := time.Now() + // first try to unmarshal the incoming request as an array of RPC requests var ( requests types.RPCRequests @@ -720,6 +765,14 @@ func (wsc *wsConnection) readRoutine() { if len(responses) > 0 { wsc.WriteRPCResponses(responses) + + // Log telemetry + if telemetryEnabled { + metrics.WSRequestTime.Record( + context.Background(), + time.Since(responseStart).Milliseconds(), + ) + } } } } diff --git a/tm2/pkg/bft/state/eventstore/types/config.go b/tm2/pkg/bft/state/eventstore/types/config.go index 5b152f254fd..a74d6c9d2ec 100644 --- a/tm2/pkg/bft/state/eventstore/types/config.go +++ b/tm2/pkg/bft/state/eventstore/types/config.go @@ -7,8 +7,8 @@ type EventStoreParams map[string]any // Config defines the specific event store configuration type Config struct { - EventStoreType string `toml:"event_store_type" comment:"Type of event store"` - Params EventStoreParams `toml:"event_store_params" comment:"Event store parameters"` + EventStoreType string `json:"event_store_type" toml:"event_store_type" comment:"Type of event store"` + Params EventStoreParams `json:"event_store_params" toml:"event_store_params" comment:"Event store parameters"` } // GetParam fetches the specific config param, if any. diff --git a/tm2/pkg/bft/state/execution.go b/tm2/pkg/bft/state/execution.go index da1735e3fae..15a0f466341 100644 --- a/tm2/pkg/bft/state/execution.go +++ b/tm2/pkg/bft/state/execution.go @@ -106,10 +106,10 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b fail.Fail() // XXX - // Save the results before we commit. - saveABCIResponses(blockExec.db, block.Height, abciResponses) + // Save the results by height + SaveABCIResponses(blockExec.db, block.Height, abciResponses) - // Save the transaction results + // Save the results by tx hash for index, tx := range block.Txs { saveTxResultIndex( blockExec.db, @@ -427,7 +427,7 @@ func fireEvents(evsw events.EventSwitch, block *types.Block, abciResponses *ABCI Height: block.Height, Index: uint32(i), Tx: tx, - Response: (abciResponses.DeliverTxs[i]), + Response: abciResponses.DeliverTxs[i], }}) } diff --git a/tm2/pkg/bft/state/export_test.go b/tm2/pkg/bft/state/export_test.go index cdebf3e852d..0935236ed92 100644 --- a/tm2/pkg/bft/state/export_test.go +++ b/tm2/pkg/bft/state/export_test.go @@ -42,12 +42,6 @@ func CalcValidatorsKey(height int64) []byte { return calcValidatorsKey(height) } -// SaveABCIResponses is an alias for the private saveABCIResponses method in -// store.go, exported exclusively and explicitly for testing. -func SaveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { - saveABCIResponses(db, height, abciResponses) -} - // SaveConsensusParamsInfo is an alias for the private saveConsensusParamsInfo // method in store.go, exported exclusively and explicitly for testing. func SaveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params abci.ConsensusParams) { diff --git a/tm2/pkg/bft/state/store.go b/tm2/pkg/bft/state/store.go index 804d96842c4..7c23e4eed4a 100644 --- a/tm2/pkg/bft/state/store.go +++ b/tm2/pkg/bft/state/store.go @@ -131,8 +131,13 @@ type ABCIResponses struct { // NewABCIResponses returns a new ABCIResponses func NewABCIResponses(block *types.Block) *ABCIResponses { - resDeliverTxs := make([]abci.ResponseDeliverTx, block.NumTxs) - if block.NumTxs == 0 { + return NewABCIResponsesFromNum(block.NumTxs) +} + +// NewABCIResponsesFromNum returns a new ABCIResponses with a set number of txs +func NewABCIResponsesFromNum(numTxs int64) *ABCIResponses { + resDeliverTxs := make([]abci.ResponseDeliverTx, numTxs) + if numTxs == 0 { // This makes Amino encoding/decoding consistent. resDeliverTxs = nil } @@ -175,7 +180,8 @@ func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) { // SaveABCIResponses persists the ABCIResponses to the database. // This is useful in case we crash after app.Commit and before s.Save(). // Responses are indexed by height so they can also be loaded later to produce Merkle proofs. -func saveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { +// NOTE: this should only be used internally by the bft package and subpackages. +func SaveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { db.Set(CalcABCIResponsesKey(height), abciResponses.Bytes()) } diff --git a/tm2/pkg/colors/colors.go b/tm2/pkg/colors/colors.go index d2af56c892a..84a0353d880 100644 --- a/tm2/pkg/colors/colors.go +++ b/tm2/pkg/colors/colors.go @@ -96,14 +96,80 @@ func Gray(args ...interface{}) string { return treatAll(ANSIFgGray, args...) } -func ColoredBytes(data []byte, textColor, bytesColor func(...interface{}) string) string { +// result may be 4 ASNSII chars longer than they should be to denote the +// elipses (...), and one for a trailing hex nibble in case the last byte is +// non-ascii. +// NOTE: it is annoying to try make this perfect and always fit within n, so we +// don't do this yet, but left as an exercise. :) +func ColoredBytesN(data []byte, n int, textColor, bytesColor func(...interface{}) string) string { + _n := 0 s := "" - for _, b := range data { + buf := "" // buffer + bufIsText := true // is buf text or hex + for i, b := range data { + RESTART: if 0x21 <= b && b < 0x7F { - s += textColor(string(b)) + if !bufIsText { + s += bytesColor(buf) + buf = "" + bufIsText = true + goto RESTART + } + buf += string(b) + _n += 1 + if n != 0 && _n >= n { + if i == len(data)-1 { + // done + s += textColor(buf) + buf = "" + } else { + s += textColor(buf) + "..." + buf = "" + } + break + } + } else { + if bufIsText { + s += textColor(buf) + buf = "" + bufIsText = false + goto RESTART + } + buf += fmt.Sprintf("%02X", b) + _n += 2 + if n != 0 && _n >= n { + if i == len(data)-1 { + // done + s += bytesColor(buf) + buf = "" + } else { + s += bytesColor(buf) + "..." + buf = "" + } + break + } + } + } + if buf != "" { + if bufIsText { + s += textColor(buf) + buf = "" } else { - s += bytesColor(fmt.Sprintf("%02X", b)) + s += bytesColor(buf) + buf = "" } } return s } + +func DefaultColoredBytesN(data []byte, n int) string { + return ColoredBytesN(data, n, Blue, Green) +} + +func ColoredBytes(data []byte, textColor, bytesColor func(...interface{}) string) string { + return ColoredBytesN(data, 0, textColor, bytesColor) +} + +func DefaultColoredBytes(data []byte) string { + return ColoredBytes(data, Blue, Green) +} diff --git a/tm2/pkg/commands/command.go b/tm2/pkg/commands/command.go index bc5f6f36cc5..aa717b62ad9 100644 --- a/tm2/pkg/commands/command.go +++ b/tm2/pkg/commands/command.go @@ -110,9 +110,17 @@ func (c *Command) AddSubCommands(cmds ...*Command) { // handles the flag.ErrHelp error, ensuring that every command with -h or // --help won't show an error message: // 'error parsing commandline arguments: flag: help requested' +// +// Additionally, any error of type [ErrExitCode] will be handled by exiting with +// the given status code. func (c *Command) Execute(ctx context.Context, args []string) { if err := c.ParseAndRun(ctx, args); err != nil { - if !errors.Is(err, flag.ErrHelp) { + var ece ExitCodeError + switch { + case errors.Is(err, flag.ErrHelp): // just exit with 1 (help already printed) + case errors.As(err, &ece): + os.Exit(int(ece)) + default: _, _ = fmt.Fprintf(os.Stderr, "%+v\n", err) } os.Exit(1) diff --git a/tm2/pkg/commands/errors.go b/tm2/pkg/commands/errors.go new file mode 100644 index 00000000000..641e06c39a8 --- /dev/null +++ b/tm2/pkg/commands/errors.go @@ -0,0 +1,16 @@ +package commands + +import ( + "strconv" +) + +// ExitCodeError is an error to terminate the program without printing any error, +// but passing in the given exit code to os.Exit. +// +// [Command.ParseAndRun] will return any ExitCodeError encountered, but +// [Command.Execute] will handle it and return an appropriate error message. +type ExitCodeError int + +func (e ExitCodeError) Error() string { + return "exit code: " + strconv.Itoa(int(e)) +} diff --git a/tm2/pkg/crypto/internal/ledger/discover.go b/tm2/pkg/crypto/internal/ledger/discover.go new file mode 100644 index 00000000000..a3402323938 --- /dev/null +++ b/tm2/pkg/crypto/internal/ledger/discover.go @@ -0,0 +1,83 @@ +// Package ledger contains the internals for package crypto/keys/ledger, +// primarily existing so that the Discover function can be mocked elsewhere. +package ledger + +import ( + "github.com/btcsuite/btcd/btcec/v2" + ledger_go "github.com/cosmos/ledger-cosmos-go" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" +) + +// SECP256K1 reflects an interface a Ledger API must implement for SECP256K1 +type SECP256K1 interface { + Close() error + // Returns an uncompressed pubkey + GetPublicKeySECP256K1([]uint32) ([]byte, error) + // Returns a compressed pubkey and bech32 address (requires user confirmation) + GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error) + // Signs a message (requires user confirmation) + SignSECP256K1([]uint32, []byte, byte) ([]byte, error) +} + +// Discover defines a function to be invoked at runtime for discovering +// a connected Ledger device. +var Discover DiscoverFn = DiscoverDefault + +// DiscoverDefault is the default function for [Discover]. +func DiscoverDefault() (SECP256K1, error) { + device, err := ledger_go.FindLedgerCosmosUserApp() + if err != nil { + return nil, err + } + + return device, nil +} + +// DiscoverMock can be used as a mock [DiscoverFn]. +func DiscoverMock() (SECP256K1, error) { + privateKey := secp256k1.GenPrivKey() + + _, pubKeyObject := btcec.PrivKeyFromBytes(privateKey[:]) + return discoverMock{ + pubKey: pubKeyObject.SerializeCompressed(), + address: privateKey.PubKey().Address().String(), + }, nil +} + +type discoverMock struct { + MockLedger + pubKey []byte + address string +} + +func (m discoverMock) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) { + return m.pubKey, m.address, nil +} + +// MockLedger is an interface that can be used to create mock [Ledger]. +// Embed it in another type, and implement the method you want to mock: +// +// type MyMock struct { MockLedger } +// func (MyMock) SignSECP256K1(d1, d2 []byte, d3 byte) ([]byte, error) { ... } +type MockLedger struct{} + +func (MockLedger) Close() error { + return nil +} + +func (MockLedger) GetPublicKeySECP256K1(data []uint32) ([]byte, error) { + return nil, nil +} + +func (MockLedger) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) { + return nil, "", nil +} + +func (MockLedger) SignSECP256K1(d1 []uint32, d2 []byte, d3 byte) ([]byte, error) { + return nil, nil +} + +// DiscoverFn defines a Ledger discovery function that returns a +// connected device or an error upon failure. Its allows a method to avoid CGO +// dependencies when Ledger support is potentially not enabled. +type DiscoverFn func() (SECP256K1, error) diff --git a/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go b/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go deleted file mode 100644 index 8a09d060b16..00000000000 --- a/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !ledger_suite -// +build !ledger_suite - -package client - -import "testing" - -func TestAdd_Ledger(t *testing.T) { - t.Skip("Please enable the 'ledger_suite' build tags") -} diff --git a/tm2/pkg/crypto/keys/client/add_ledger_test.go b/tm2/pkg/crypto/keys/client/add_ledger_test.go index c1384efcb79..676c4bff6b4 100644 --- a/tm2/pkg/crypto/keys/client/add_ledger_test.go +++ b/tm2/pkg/crypto/keys/client/add_ledger_test.go @@ -1,6 +1,3 @@ -//go:build ledger_suite -// +build ledger_suite - package client import ( @@ -10,19 +7,17 @@ import ( "time" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/internal/ledger" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// Make sure to run these tests with the following tag enabled: -// -tags='ledger_suite' func TestAdd_Ledger(t *testing.T) { - t.Parallel() + ledger.Discover = ledger.DiscoverMock + t.Cleanup(func() { ledger.Discover = ledger.DiscoverDefault }) t.Run("valid ledger reference added", func(t *testing.T) { - t.Parallel() - var ( kbHome = t.TempDir() baseOptions = BaseOptions{ @@ -63,8 +58,6 @@ func TestAdd_Ledger(t *testing.T) { }) t.Run("valid ledger reference added, overwrite", func(t *testing.T) { - t.Parallel() - var ( kbHome = t.TempDir() baseOptions = BaseOptions{ @@ -116,8 +109,6 @@ func TestAdd_Ledger(t *testing.T) { }) t.Run("valid ledger reference added, no overwrite permission", func(t *testing.T) { - t.Parallel() - var ( kbHome = t.TempDir() baseOptions = BaseOptions{ diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index c088c63d19f..1cf50bc32ed 100644 --- a/tm2/pkg/crypto/keys/client/broadcast.go +++ b/tm2/pkg/crypto/keys/client/broadcast.go @@ -2,6 +2,7 @@ package client import ( "context" + "encoding/base64" "flag" "os" @@ -40,7 +41,7 @@ func NewBroadcastCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execBroadcast(cfg, args, commands.NewDefaultIO()) + return execBroadcast(cfg, args, io) }, ) } @@ -79,6 +80,7 @@ func execBroadcast(cfg *BroadcastCfg, args []string, io commands.IO) error { if res.CheckTx.IsErr() { return errors.New("transaction failed %#v\nlog %s", res, res.CheckTx.Log) } else if res.DeliverTx.IsErr() { + io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(res.Hash)) return errors.New("transaction failed %#v\nlog %s", res, res.DeliverTx.Log) } else { io.Println(string(res.DeliverTx.Data)) @@ -87,6 +89,7 @@ func execBroadcast(cfg *BroadcastCfg, args []string, io commands.IO) error { io.Println("GAS USED: ", res.DeliverTx.GasUsed) io.Println("HEIGHT: ", res.Height) io.Println("EVENTS: ", string(res.DeliverTx.EncodeEvents())) + io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(res.Hash)) } return nil } diff --git a/tm2/pkg/crypto/keys/client/export.go b/tm2/pkg/crypto/keys/client/export.go index b80942734f6..b7a82a5af6e 100644 --- a/tm2/pkg/crypto/keys/client/export.go +++ b/tm2/pkg/crypto/keys/client/export.go @@ -105,7 +105,7 @@ func execExport(cfg *ExportCfg, io commands.IO) error { panic(err) } - fmt.Printf("privk:\n%x\n", privk.Bytes()) + io.Printf("privk:\n%x\n", privk.Bytes()) } else { // Get the armor encrypt password encryptPassword, err := io.GetCheckPassword( diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 2afccf9141c..18e0b78f487 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -1,6 +1,7 @@ package client import ( + "encoding/base64" "flag" "fmt" @@ -23,6 +24,9 @@ type MakeTxCfg struct { // Valid options are SimulateTest, SimulateSkip or SimulateOnly. Simulate string ChainID string + + // Optional + Sponsor string } // These are the valid options for MakeTxConfig.Simulate. @@ -108,6 +112,13 @@ func (c *MakeTxCfg) RegisterFlags(fs *flag.FlagSet) { "dev", "chainid to sign for (only useful with --broadcast)", ) + + fs.StringVar( + &c.Sponsor, + "sponsor", + "", + "onchain address of the sponsor", + ) } func SignAndBroadcastHandler( @@ -130,24 +141,32 @@ func SignAndBroadcastHandler( } accountAddr := info.GetAddress() + var accountNumber uint64 + var sequence uint64 + qopts := &QueryCfg{ RootCfg: baseopts, Path: fmt.Sprintf("auth/accounts/%s", accountAddr), } qres, err := QueryHandler(qopts) if err != nil { - return nil, errors.Wrap(err, "query account") - } - var qret struct{ BaseAccount std.BaseAccount } - err = amino.UnmarshalJSON(qres.Response.Data, &qret) - if err != nil { - return nil, err + if !tx.IsSponsorTx() { + return nil, errors.Wrap(err, "query account") + } + } else { + var qret struct { + BaseAccount std.BaseAccount + } + + err = amino.UnmarshalJSON(qres.Response.Data, &qret) + if err != nil { + return nil, err + } + accountNumber = qret.BaseAccount.AccountNumber + sequence = qret.BaseAccount.Sequence } // sign tx - accountNumber := qret.BaseAccount.AccountNumber - sequence := qret.BaseAccount.Sequence - sOpts := signOpts{ chainID: txopts.ChainID, accountSequence: sequence, @@ -210,6 +229,7 @@ func ExecSignAndBroadcast( return errors.Wrap(bres.CheckTx.Error, "check transaction failed: log:%s", bres.CheckTx.Log) } if bres.DeliverTx.IsErr() { + io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(bres.Hash)) return errors.Wrap(bres.DeliverTx.Error, "deliver transaction failed: log:%s", bres.DeliverTx.Log) } @@ -219,6 +239,7 @@ func ExecSignAndBroadcast( io.Println("GAS USED: ", bres.DeliverTx.GasUsed) io.Println("HEIGHT: ", bres.Height) io.Println("EVENTS: ", string(bres.DeliverTx.EncodeEvents())) + io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(bres.Hash)) return nil } diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index f4b65adebc0..3278d0ea0ea 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -13,10 +13,7 @@ import ( type QueryCfg struct { RootCfg *BaseCfg - Data string - Height int64 - Prove bool - + Data string Path string } @@ -45,20 +42,6 @@ func (c *QueryCfg) RegisterFlags(fs *flag.FlagSet) { "", "query data bytes", ) - - fs.Int64Var( - &c.Height, - "height", - 0, - "query height (not yet supported)", - ) - - fs.BoolVar( - &c.Prove, - "prove", - false, - "prove query result (not yet supported)", - ) } func execQuery(cfg *QueryCfg, args []string, io commands.IO) error { diff --git a/tm2/pkg/crypto/keys/client/send.go b/tm2/pkg/crypto/keys/client/send.go index 964c0a86993..c5368c73996 100644 --- a/tm2/pkg/crypto/keys/client/send.go +++ b/tm2/pkg/crypto/keys/client/send.go @@ -3,8 +3,8 @@ package client import ( "context" "flag" - "fmt" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -105,12 +105,35 @@ func execMakeSend(cfg *MakeSendCfg, args []string, io commands.IO) error { return errors.Wrap(err, "parsing gas fee coin") } - // construct msg & tx and marshal. msg := bank.MsgSend{ FromAddress: fromAddr, ToAddress: toAddr, Amount: send, } + + // if a sponsor onchain address is specified + if cfg.RootCfg.Sponsor != "" && !cfg.RootCfg.Broadcast { + sponsorAddress, err := crypto.AddressFromBech32(cfg.RootCfg.Sponsor) + if err != nil { + return errors.Wrap(err, "invalid sponsor address") + } + + tx := std.Tx{ + Msgs: []std.Msg{vm.NewMsgNoop(sponsorAddress), msg}, + Fee: std.NewFee(gaswanted, gasfee), + Signatures: nil, + Memo: cfg.RootCfg.Memo, + } + + if cfg.RootCfg.Broadcast { + return ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) + } + + io.Println(string(amino.MustMarshalJSON(tx))) + + return nil + } + tx := std.Tx{ Msgs: []std.Msg{msg}, Fee: std.NewFee(gaswanted, gasfee), @@ -124,7 +147,8 @@ func execMakeSend(cfg *MakeSendCfg, args []string, io commands.IO) error { return err } } else { - fmt.Println(string(amino.MustMarshalJSON(tx))) + io.Println(string(amino.MustMarshalJSON(tx))) } + return nil } diff --git a/tm2/pkg/crypto/keys/client/sign.go b/tm2/pkg/crypto/keys/client/sign.go index 87833165063..3509c9affd5 100644 --- a/tm2/pkg/crypto/keys/client/sign.go +++ b/tm2/pkg/crypto/keys/client/sign.go @@ -157,6 +157,24 @@ func execSign(cfg *SignCfg, args []string, io commands.IO) error { } } + // Check if accountNumber or sequence is zero to determine if account information needs to be fetched + if cfg.AccountNumber == 0 || cfg.Sequence == 0 { + accountAddr := info.GetAddress() + + qopts := &QueryCfg{ + RootCfg: cfg.RootCfg, + Path: fmt.Sprintf("auth/accounts/%s", accountAddr), + } + qres, err := QueryHandler(qopts) + if err == nil { + var qret struct{ BaseAccount std.BaseAccount } + if err := amino.UnmarshalJSON(qres.Response.Data, &qret); err == nil { + cfg.AccountNumber = qret.BaseAccount.AccountNumber + cfg.Sequence = qret.BaseAccount.Sequence + } + } + } + // Prepare the signature ops sOpts := signOpts{ chainID: cfg.ChainID, @@ -185,6 +203,22 @@ func signTx( signOpts signOpts, keyOpts keyOpts, ) error { + // Save the signature + signers := tx.GetSigners() + if tx.Signatures == nil { + for range signers { + tx.Signatures = append(tx.Signatures, std.Signature{ + PubKey: nil, // Zero signature + Signature: nil, // Zero signature + }) + } + } + + // Validate the tx after signing + if err := tx.ValidateBasic(); err != nil { + return fmt.Errorf("unable to validate transaction, %w", err) + } + signBytes, err := tx.GetSignBytes( signOpts.chainID, signOpts.accountNumber, @@ -204,38 +238,21 @@ func signTx( return fmt.Errorf("unable to sign transaction bytes, %w", err) } - // Save the signature - if tx.Signatures == nil { - tx.Signatures = make([]std.Signature, 0, 1) - } + addr := pub.Address() - // Check if the signature needs to be overwritten - for index, signature := range tx.Signatures { - if !signature.PubKey.Equals(pub) { - continue + found := false + for i := range tx.Signatures { + if signers[i] == addr { + found = true + tx.Signatures[i] = std.Signature{ + PubKey: pub, + Signature: sig, + } } - - // Save the signature - tx.Signatures[index] = std.Signature{ - PubKey: pub, - Signature: sig, - } - - return nil } - // Append the signature, since it wasn't - // present before - tx.Signatures = append( - tx.Signatures, std.Signature{ - PubKey: pub, - Signature: sig, - }, - ) - - // Validate the tx after signing - if err := tx.ValidateBasic(); err != nil { - return fmt.Errorf("unable to validate transaction, %w", err) + if !found { + return fmt.Errorf("address %v (%s) not in signer set", addr, keyOpts.keyName) } return nil diff --git a/tm2/pkg/crypto/keys/client/sign_test.go b/tm2/pkg/crypto/keys/client/sign_test.go index eb30dc17162..f8034e11852 100644 --- a/tm2/pkg/crypto/keys/client/sign_test.go +++ b/tm2/pkg/crypto/keys/client/sign_test.go @@ -403,126 +403,6 @@ func TestSign_SignTx(t *testing.T) { assert.True(t, savedTx.Signatures[0].PubKey.Equals(info.GetPubKey())) }) - t.Run("existing signature list", func(t *testing.T) { - t.Parallel() - - var ( - kbHome = t.TempDir() - baseOptions = BaseOptions{ - InsecurePasswordStdin: true, - Home: kbHome, - Quiet: true, - } - - mnemonic = generateTestMnemonic(t) - keyName = "generated-key" - encryptPassword = "encrypt" - - anotherKey = "another-key" - - tx = std.Tx{ - Fee: std.Fee{ - GasWanted: 10, - GasFee: std.Coin{ - Amount: 10, - Denom: "ugnot", - }, - }, - } - ) - - // Generate a key in the keybase - kb, err := keys.NewKeyBaseFromDir(kbHome) - require.NoError(t, err) - - // Create an initial account - info, err := kb.CreateAccount(keyName, mnemonic, "", encryptPassword, 0, 0) - require.NoError(t, err) - - // Create a new account - anotherKeyInfo, err := kb.CreateAccount(anotherKey, mnemonic, "", encryptPassword, 0, 1) - require.NoError(t, err) - - // Generate the signature - signBytes, err := tx.GetSignBytes("id", 1, 0) - require.NoError(t, err) - - signature, pubKey, err := kb.Sign(anotherKey, encryptPassword, signBytes) - require.NoError(t, err) - - tx.Signatures = []std.Signature{ - { - PubKey: pubKey, - Signature: signature, - }, - } - - // We need to prepare the message signers as well - // for validation to complete - tx.Msgs = []std.Msg{ - bank.MsgSend{ - FromAddress: info.GetAddress(), - }, - bank.MsgSend{ - FromAddress: anotherKeyInfo.GetAddress(), - }, - } - - // Create an empty tx file - txFile, err := os.CreateTemp("", "") - require.NoError(t, err) - - // Marshal the tx and write it to the file - encodedTx, err := amino.MarshalJSON(tx) - require.NoError(t, err) - - _, err = txFile.Write(encodedTx) - require.NoError(t, err) - - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) - defer cancelFn() - - // Create the command IO - io := commands.NewTestIO() - io.SetIn( - strings.NewReader( - fmt.Sprintf( - "%s\n%s\n", - encryptPassword, - encryptPassword, - ), - ), - ) - - // Create the command - cmd := NewRootCmdWithBaseConfig(io, baseOptions) - - args := []string{ - "sign", - "--insecure-password-stdin", - "--home", - kbHome, - "--tx-path", - txFile.Name(), - keyName, - } - - // Run the command - require.NoError(t, cmd.ParseAndRun(ctx, args)) - - // Make sure the tx file was updated with the signature - savedTxRaw, err := os.ReadFile(txFile.Name()) - require.NoError(t, err) - - var savedTx std.Tx - require.NoError(t, amino.UnmarshalJSON(savedTxRaw, &savedTx)) - - require.Len(t, savedTx.Signatures, 2) - assert.True(t, savedTx.Signatures[0].PubKey.Equals(anotherKeyInfo.GetPubKey())) - assert.True(t, savedTx.Signatures[1].PubKey.Equals(info.GetPubKey())) - assert.NotEqual(t, savedTx.Signatures[0].Signature, savedTx.Signatures[1].Signature) - }) - t.Run("overwrite existing signature", func(t *testing.T) { t.Parallel() diff --git a/tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go b/tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go deleted file mode 100644 index d406f10f2ed..00000000000 --- a/tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !ledger_suite -// +build !ledger_suite - -package keys - -import "testing" - -func TestCreateLedgerUnsupportedAlgo(t *testing.T) { - t.Parallel() - - t.Skip("this test needs to be run with the `ledger_suite` tag enabled") -} - -func TestCreateLedger(t *testing.T) { - t.Parallel() - - t.Skip("this test needs to be run with the `ledger_suite` tag enabled") -} diff --git a/tm2/pkg/crypto/keys/keybase_ledger_test.go b/tm2/pkg/crypto/keys/keybase_ledger_test.go index 0f2fca79f90..ea980cf4f02 100644 --- a/tm2/pkg/crypto/keys/keybase_ledger_test.go +++ b/tm2/pkg/crypto/keys/keybase_ledger_test.go @@ -1,17 +1,16 @@ -//go:build ledger_suite -// +build ledger_suite - package keys import ( "testing" + "github.com/gnolang/gno/tm2/pkg/crypto/internal/ledger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateLedgerUnsupportedAlgo(t *testing.T) { - t.Parallel() + ledger.Discover = ledger.DiscoverMock + t.Cleanup(func() { ledger.Discover = ledger.DiscoverDefault }) kb := NewInMemory() _, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1) @@ -20,7 +19,8 @@ func TestCreateLedgerUnsupportedAlgo(t *testing.T) { } func TestCreateLedger(t *testing.T) { - t.Parallel() + ledger.Discover = ledger.DiscoverMock + t.Cleanup(func() { ledger.Discover = ledger.DiscoverDefault }) kb := NewInMemory() diff --git a/tm2/pkg/crypto/ledger/discover.go b/tm2/pkg/crypto/ledger/discover.go deleted file mode 100644 index d610b56635e..00000000000 --- a/tm2/pkg/crypto/ledger/discover.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build !ledger_suite -// +build !ledger_suite - -package ledger - -import ( - ledger_go "github.com/cosmos/ledger-cosmos-go" -) - -// discoverLedger defines a function to be invoked at runtime for discovering -// a connected Ledger device. -var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) { - device, err := ledger_go.FindLedgerCosmosUserApp() - if err != nil { - return nil, err - } - - return device, nil -} diff --git a/tm2/pkg/crypto/ledger/discover_mock.go b/tm2/pkg/crypto/ledger/discover_mock.go deleted file mode 100644 index 1f5bdbafdf3..00000000000 --- a/tm2/pkg/crypto/ledger/discover_mock.go +++ /dev/null @@ -1,69 +0,0 @@ -//go:build ledger_suite -// +build ledger_suite - -package ledger - -import ( - btcec "github.com/btcsuite/btcd/btcec/v2" - "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" -) - -// discoverLedger defines a function to be invoked at runtime for discovering -// a connected Ledger device. -var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) { - privateKey := secp256k1.GenPrivKey() - - _, pubKeyObject := btcec.PrivKeyFromBytes(privateKey[:]) - - return &MockLedger{ - GetAddressPubKeySECP256K1Fn: func(data []uint32, str string) ([]byte, string, error) { - return pubKeyObject.SerializeCompressed(), privateKey.PubKey().Address().String(), nil - }, - }, nil -} - -type ( - closeDelegate func() error - getPublicKeySECP256K1Delegate func([]uint32) ([]byte, error) - getAddressPubKeySECP256K1Delegate func([]uint32, string) ([]byte, string, error) - signSECP256K1Delegate func([]uint32, []byte, byte) ([]byte, error) -) - -type MockLedger struct { - CloseFn closeDelegate - GetPublicKeySECP256K1Fn getPublicKeySECP256K1Delegate - GetAddressPubKeySECP256K1Fn getAddressPubKeySECP256K1Delegate - SignSECP256K1Fn signSECP256K1Delegate -} - -func (m *MockLedger) Close() error { - if m.CloseFn != nil { - return m.CloseFn() - } - - return nil -} - -func (m *MockLedger) GetPublicKeySECP256K1(data []uint32) ([]byte, error) { - if m.GetPublicKeySECP256K1Fn != nil { - return m.GetPublicKeySECP256K1Fn(data) - } - - return nil, nil -} - -func (m *MockLedger) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) { - if m.GetAddressPubKeySECP256K1Fn != nil { - return m.GetAddressPubKeySECP256K1Fn(data, str) - } - - return nil, "", nil -} - -func (m *MockLedger) SignSECP256K1(d1 []uint32, d2 []byte, d3 byte) ([]byte, error) { - if m.SignSECP256K1Fn != nil { - return m.SignSECP256K1Fn(d1, d2, d3) - } - - return nil, nil -} diff --git a/tm2/pkg/crypto/ledger/ledger_secp256k1.go b/tm2/pkg/crypto/ledger/ledger_secp256k1.go index 56877b813a5..1d0ac8b05f0 100644 --- a/tm2/pkg/crypto/ledger/ledger_secp256k1.go +++ b/tm2/pkg/crypto/ledger/ledger_secp256k1.go @@ -12,27 +12,12 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/hd" + "github.com/gnolang/gno/tm2/pkg/crypto/internal/ledger" "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" "github.com/gnolang/gno/tm2/pkg/errors" ) type ( - // discoverLedgerFn defines a Ledger discovery function that returns a - // connected device or an error upon failure. Its allows a method to avoid CGO - // dependencies when Ledger support is potentially not enabled. - discoverLedgerFn func() (LedgerSECP256K1, error) - - // LedgerSECP256K1 reflects an interface a Ledger API must implement for SECP256K1 - LedgerSECP256K1 interface { - Close() error - // Returns an uncompressed pubkey - GetPublicKeySECP256K1([]uint32) ([]byte, error) - // Returns a compressed pubkey and bech32 address (requires user confirmation) - GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error) - // Signs a message (requires user confirmation) - SignSECP256K1([]uint32, []byte, byte) ([]byte, error) - } - // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we // cache the PubKey from the first call to use it later. PrivKeyLedgerSecp256k1 struct { @@ -191,8 +176,8 @@ func convertDERtoBER(signatureDER []byte) ([]byte, error) { return sigBytes, nil } -func getLedgerDevice() (LedgerSECP256K1, error) { - device, err := discoverLedger() +func getLedgerDevice() (ledger.SECP256K1, error) { + device, err := ledger.Discover() if err != nil { return nil, errors.Wrap(err, "ledger nano S") } @@ -200,7 +185,7 @@ func getLedgerDevice() (LedgerSECP256K1, error) { return device, nil } -func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error { +func validateKey(device ledger.SECP256K1, pkl PrivKeyLedgerSecp256k1) error { pub, err := getPubKeyUnsafe(device, pkl.Path) if err != nil { return err @@ -219,7 +204,7 @@ func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error { // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning // an error, so this should only trigger if the private key is held in memory // for a while before use. -func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) { +func sign(device ledger.SECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) { err := validateKey(device, pkl) if err != nil { return nil, err @@ -240,7 +225,7 @@ func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byt // // since this involves IO, it may return an error, which is not exposed // in the PubKey interface, so this function allows better error handling -func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (crypto.PubKey, error) { +func getPubKeyUnsafe(device ledger.SECP256K1, path hd.BIP44Params) (crypto.PubKey, error) { publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath()) if err != nil { return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %w", err) @@ -264,7 +249,7 @@ func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (crypto.PubKey // // Since this involves IO, it may return an error, which is not exposed // in the PubKey interface, so this function allows better error handling. -func getPubKeyAddrSafe(device LedgerSECP256K1, path hd.BIP44Params, hrp string) (crypto.PubKey, string, error) { +func getPubKeyAddrSafe(device ledger.SECP256K1, path hd.BIP44Params, hrp string) (crypto.PubKey, string, error) { publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp) if err != nil { return nil, "", fmt.Errorf("address %s rejected", addr) diff --git a/tm2/pkg/db/goleveldb/go_level_db.go b/tm2/pkg/db/goleveldb/go_level_db.go index 27db61bb680..4f011cb8395 100644 --- a/tm2/pkg/db/goleveldb/go_level_db.go +++ b/tm2/pkg/db/goleveldb/go_level_db.go @@ -6,6 +6,7 @@ import ( "fmt" "path/filepath" + "github.com/gnolang/gno/tm2/pkg/colors" "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/internal" "github.com/syndtr/goleveldb/leveldb" @@ -115,9 +116,9 @@ func (db *GoLevelDB) Print() { itr := db.db.NewIterator(nil, nil) for itr.Next() { - key := itr.Key() - value := itr.Value() - fmt.Printf("[%X]:\t[%X]\n", key, value) + key := colors.DefaultColoredBytesN(itr.Key(), 50) + value := colors.DefaultColoredBytesN(itr.Value(), 100) + fmt.Printf("%v: %v\n", key, value) } } diff --git a/tm2/pkg/db/memdb/mem_db.go b/tm2/pkg/db/memdb/mem_db.go index 6d1d6a35af9..09b90b6be44 100644 --- a/tm2/pkg/db/memdb/mem_db.go +++ b/tm2/pkg/db/memdb/mem_db.go @@ -5,9 +5,9 @@ import ( "sort" "sync" + "github.com/gnolang/gno/tm2/pkg/colors" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/internal" - "github.com/gnolang/gno/tm2/pkg/strings" ) func init() { @@ -128,17 +128,9 @@ func (db *MemDB) Print() { for key, value := range db.db { var keystr, valstr string - if strings.IsASCIIText(key) { - keystr = key - } else { - keystr = fmt.Sprintf("0x%X", []byte(key)) - } - if strings.IsASCIIText(string(value)) { - valstr = string(value) - } else { - valstr = fmt.Sprintf("0x%X", value) - } - fmt.Printf("%s:\t%s\n", keystr, valstr) + keystr = colors.DefaultColoredBytesN([]byte(key), 50) + valstr = colors.DefaultColoredBytesN(value, 100) + fmt.Printf("%s: %s\n", keystr, valstr) } } diff --git a/tm2/pkg/db/prefix_db.go b/tm2/pkg/db/prefix_db.go index 29ed53639e8..a38850c2173 100644 --- a/tm2/pkg/db/prefix_db.go +++ b/tm2/pkg/db/prefix_db.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "sync" + + "github.com/gnolang/gno/tm2/pkg/colors" ) // IteratePrefix is a convenience function for iterating over a key domain @@ -188,15 +190,10 @@ func (pdb *PrefixDB) Close() { // Implements DB. func (pdb *PrefixDB) Print() { - fmt.Printf("prefix: %X\n", pdb.prefix) - - itr := pdb.Iterator(nil, nil) - defer itr.Close() - for ; itr.Valid(); itr.Next() { - key := itr.Key() - value := itr.Value() - fmt.Printf("[%X]:\t[%X]\n", key, value) - } + fmt.Println(colors.Blue("prefix ---------------------")) + fmt.Printf("prefix: %v\n", colors.DefaultColoredBytes(pdb.prefix)) + pdb.db.Print() + fmt.Println(colors.Blue("prefix --------------------- end")) } // Implements DB. diff --git a/tm2/pkg/errors/errors.go b/tm2/pkg/errors/errors.go index c07356f0b06..c72d9c64680 100644 --- a/tm2/pkg/errors/errors.go +++ b/tm2/pkg/errors/errors.go @@ -237,6 +237,9 @@ type FmtError struct { } func (fe FmtError) Error() string { + if len(fe.args) == 0 { + return fe.format + } return fmt.Sprintf(fe.format, fe.args...) } diff --git a/tm2/pkg/libtm/.gitignore b/tm2/pkg/libtm/.gitignore new file mode 100644 index 00000000000..15cf730b967 --- /dev/null +++ b/tm2/pkg/libtm/.gitignore @@ -0,0 +1,9 @@ +# MacOS Leftovers +.DS_Store + +# Editor Leftovers +.vscode +.idea + +# Log files +*.log diff --git a/tm2/pkg/libtm/LICENSE b/tm2/pkg/libtm/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/tm2/pkg/libtm/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tm2/pkg/libtm/Makefile b/tm2/pkg/libtm/Makefile new file mode 100644 index 00000000000..0b6622582f6 --- /dev/null +++ b/tm2/pkg/libtm/Makefile @@ -0,0 +1,19 @@ +.PHONY: lint +lint: + golangci-lint run --config golangci.yaml + +.PHONY: gofumpt +gofumpt: + go install mvdan.cc/gofumpt@latest + gofumpt -l -w . + +.PHONY: fixalign +fixalign: + go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest + fieldalignment -fix $(filter-out $@,$(MAKECMDGOALS)) # the full package name (not path!) + +.PHONY: protoc +protoc: + # Make sure the following prerequisites are installed before running these commands: + # https://grpc.io/docs/languages/go/quickstart/#prerequisites + protoc --go_out=./ ./messages/types/proto/*.proto diff --git a/tm2/pkg/libtm/README.md b/tm2/pkg/libtm/README.md new file mode 100644 index 00000000000..bf58440e85e --- /dev/null +++ b/tm2/pkg/libtm/README.md @@ -0,0 +1,160 @@ +## Overview + +`libtm` is a simple, minimal and compact Go library that implements the Tendermint consensus engine. + +The implementation is based on Algorithm 1, of +the [Tendermint consensus whitepaper](https://arxiv.org/pdf/1807.04938.pdf) and +(more broadly) the [official Tendermint wiki](https://github.com/tendermint/tendermint/wiki). + +There are some implementation design decisions taken by the package authors: + +- it doesn't manage validator sets internally +- it doesn't implement a networking layer, or any kind of broadcast communication +- it doesn't assume, or implement, any kind of signature manipulation logic + +All of these responsibilities are left to the calling context, in the form of interface implementations. +The reason for these choices is simple - _to keep the library minimal_. + +> [!NOTE] +> We aim to make [libtm](https://github.com/gnolang/libtm) an independent project, both in terms of repository and +> governance. This will be pursued once we have successfully integrated `libtm` with `tm2` and demonstrated that the +> codebase and its API are stable and reliable for broader use. Until this integration is complete and stability is +> confirmed, `libtm` will continue to be improved upon within the current structure, in the gno monorepo. + +### What this library is + +This library is meant to be used as a consensus engine base for any distributed system that needs such functionality. +It is not exclusively made for the blockchain context -- you will find no mention or assumptions of blockchains in the +source code. + +### What this library is not + +This library is _not_ meant to replace your entire consensus setup. + +As mentioned before, certain design decisions have been taken to keep the source code minimal, which results in the +calling context being a bit more involved in orchestration. + +Please, before deciding to utilize this library in your project, understand the different moving parts and their +requirements. + +## Installation + +To get up and running with the `libtm` package, you can add it to your project using: + +```shell +go get -u github.com/gnolang/libtm +``` + +Currently, the minimum required go version is `go 1.22`. + +## Usage Examples + +```go +package main + +import ( + "context" + + "github.com/gnolang/libtm/core" + "github.com/gnolang/libtm/messages/types" +) + +// Verifier implements the libtm Verifier interface. +// Verifier is an abstraction over the outer consensus calling context +// that has access to validator set information +type Verifier struct { + // ... +} + +// Node implements the libtm Node interface. +// The Node interface is an abstraction over a single entity (current process) that runs +// the consensus algorithm +type Node struct { + // ... +} + +// Broadcast implements the libtm Broadcast interface. +// Broadcast is an abstraction over the networking / message sharing interface +// that enables message passing between validators +type Broadcast struct { + // ... +} + +// Signer implements the libtm Signer interface. +// Signer is an abstraction over the signature manipulation process +type Signer struct { + // ... +} + +// ... + +func main() { + // verifier, node, broadcast, signer, opts + var ( + verifier = NewVerifier() + node = NewNode() + broadcast = NewBroadcast() + signer = NewSigner() + ) + + tm := core.NewTendermint( + verifier, + node, + broadcast, + signer, + ) + + height := uint64(1) + ctx, cancelFn := context.WithCancel(context.Background()) + + go func() { + // Run the consensus sequence for the given height. + // When the method returns the finalized proposal, that means that + // consensus was reached within the given height (in any round) + finalizedProposal := tm.RunSequence(ctx, height) + + // Use the finalized proposal + // ... + }() + + go func() { + // Pipe messages into the consensus engine + var proposalMessage *types.ProposalMessage + + if err := tm.AddProposalMessage(proposalMessage); err != nil { + // ... + } + + // ... + + var prevoteMessage *types.PrevoteMessage + + if err := tm.AddPrevoteMessage(prevoteMessage); err != nil { + // ... + } + + // ... + + var precommitMessage *types.PrecommitMessage + + if err := tm.AddPrecommitMessage(precommitMessage); err != nil { + // ... + } + }() + + // ... + + // Stop the sequence at any time by cancelling the context + cancelFn() +} + +``` + +### Additional Options + +You can utilize additional options when creating the `Tendermint` consensus engine instance: + +- `WithLogger` - specifies the logger for the Tendermint consensus engine (slog) +- `WithProposeTimeout` specifies the propose state timeout +- `WithPrevoteTimeout` specifies the prevote state timeout +- `WithPrecommitTimeout` specifies the precommit state timeout diff --git a/tm2/pkg/libtm/core/broadcast.go b/tm2/pkg/libtm/core/broadcast.go new file mode 100644 index 00000000000..f636687a10a --- /dev/null +++ b/tm2/pkg/libtm/core/broadcast.go @@ -0,0 +1,77 @@ +package core + +import ( + "github.com/gnolang/libtm/messages/types" +) + +// buildProposalMessage builds a proposal message using the given proposal +func (t *Tendermint) buildProposalMessage(proposal []byte, proposalRound int64) *types.ProposalMessage { + var ( + height = t.state.getHeight() + round = t.state.getRound() + ) + + // Build the proposal message (assumes the node will sign it) + message := &types.ProposalMessage{ + View: &types.View{ + Height: height, + Round: round, + }, + Sender: t.node.ID(), + Proposal: proposal, + ProposalRound: proposalRound, + } + + // Sign the message + message.Signature = t.signer.Sign(message.GetSignaturePayload()) + + return message +} + +// buildPrevoteMessage builds a prevote message using the given proposal identifier +func (t *Tendermint) buildPrevoteMessage(id []byte) *types.PrevoteMessage { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + processID = t.node.ID() + ) + + message := &types.PrevoteMessage{ + View: &types.View{ + Height: height, + Round: round, + }, + Sender: processID, + Identifier: id, + } + + // Sign the message + message.Signature = t.signer.Sign(message.GetSignaturePayload()) + + return message +} + +// buildPrecommitMessage builds a precommit message using the given precommit identifier +func (t *Tendermint) buildPrecommitMessage(id []byte) *types.PrecommitMessage { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + processID = t.node.ID() + ) + + message := &types.PrecommitMessage{ + View: &types.View{ + Height: height, + Round: round, + }, + Sender: processID, + Identifier: id, + } + + // Sign the message + message.Signature = t.signer.Sign(message.GetSignaturePayload()) + + return message +} diff --git a/tm2/pkg/libtm/core/cache.go b/tm2/pkg/libtm/core/cache.go new file mode 100644 index 00000000000..3535f528c25 --- /dev/null +++ b/tm2/pkg/libtm/core/cache.go @@ -0,0 +1,63 @@ +package core + +import ( + "github.com/gnolang/libtm/messages/types" +) + +// msgType is the combined message type interface +type msgType interface { + *types.ProposalMessage | *types.PrevoteMessage | *types.PrecommitMessage +} + +type cacheMessage interface { + msgType + Message +} + +// messageCache contains filtered messages +// added in by the calling context +type messageCache[T cacheMessage] struct { + isValidFn func(T) bool + seenMap map[string]struct{} + filteredMessages []T +} + +// newMessageCache creates a new incoming message cache +func newMessageCache[T cacheMessage](isValidFn func(T) bool) messageCache[T] { + return messageCache[T]{ + isValidFn: isValidFn, + filteredMessages: make([]T, 0), + seenMap: make(map[string]struct{}), + } +} + +// addMessages pushes a new message list that is filtered +// and parsed by the cache +func (c *messageCache[T]) addMessages(messages []T) { + for _, message := range messages { + sender := message.GetSender() + + // Check if the message has been seen in the past + _, seen := c.seenMap[string(sender)] + if seen { + continue + } + + // Filter the message + if !c.isValidFn(message) { + continue + } + + // Mark the message as seen + c.seenMap[string(sender)] = struct{}{} + + // Save the message as it's + // been filtered, and doesn't exist in the cache + c.filteredMessages = append(c.filteredMessages, message) + } +} + +// getMessages returns the filtered out messages from the cache +func (c *messageCache[T]) getMessages() []T { + return c.filteredMessages +} diff --git a/tm2/pkg/libtm/core/cache_test.go b/tm2/pkg/libtm/core/cache_test.go new file mode 100644 index 00000000000..ea878aacda0 --- /dev/null +++ b/tm2/pkg/libtm/core/cache_test.go @@ -0,0 +1,70 @@ +package core + +import ( + "testing" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" +) + +func TestMessageCache_AddMessages(t *testing.T) { + t.Parallel() + + isValidFn := func(_ *types.PrevoteMessage) bool { + return true + } + + t.Run("non-duplicate messages", func(t *testing.T) { + t.Parallel() + + // Create the cache + cache := newMessageCache[*types.PrevoteMessage](isValidFn) + + // Generate non-duplicate messages + messages := generatePrevoteMessages(t, 10, &types.View{}, nil) + + // Add the messages + cache.addMessages(messages) + + // Make sure all messages are added + fetchedMessages := cache.getMessages() + + for index, message := range messages { + assert.True(t, message.Equals(fetchedMessages[index])) + } + }) + + t.Run("duplicate messages", func(t *testing.T) { + t.Parallel() + + var ( + numMessages = 10 + numDuplicates = numMessages / 2 + ) + + // Create the cache + cache := newMessageCache[*types.PrevoteMessage](isValidFn) + + // Generate non-duplicate messages + messages := generatePrevoteMessages(t, numMessages, &types.View{}, nil) + + // Make sure some are duplicated + for i := 0; i < numDuplicates; i++ { + messages[i].Sender = []byte("common sender") + } + + expectedMessages := messages[numDuplicates-1:] + + // Add the messages + cache.addMessages(messages) + + // Make sure all messages are added + fetchedMessages := cache.getMessages() + + assert.Len(t, fetchedMessages, len(expectedMessages)) + + for index, message := range expectedMessages { + assert.True(t, message.Equals(fetchedMessages[index])) + } + }) +} diff --git a/tm2/pkg/libtm/core/cluster_test.go b/tm2/pkg/libtm/core/cluster_test.go new file mode 100644 index 00000000000..2a3fbbfd763 --- /dev/null +++ b/tm2/pkg/libtm/core/cluster_test.go @@ -0,0 +1,435 @@ +package core + +import ( + "bytes" + "fmt" + "testing" + "time" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateNodeAddresses generates dummy node addresses +func generateNodeAddresses(count uint64) [][]byte { + addresses := make([][]byte, count) + + for index := range addresses { + addresses[index] = []byte(fmt.Sprintf("node %d", index)) + } + + return addresses +} + +// TestConsensus_ValidFlow tests the following scenario: +// N = 4 +// +// - Node 0 is the proposer for block 1, round 0 +// - Node 0 proposes a valid block B +// - All nodes go through the consensus states to insert the valid block B +func TestConsensus_ValidFlow(t *testing.T) { + t.Parallel() + + var ( + broadcastProposeFn func(message *types.ProposalMessage) + broadcastPrevoteFn func(message *types.PrevoteMessage) + broadcastPrecommitFn func(message *types.PrecommitMessage) + + proposal = []byte("proposal") + proposalHash = []byte("proposal hash") + signature = []byte("signature") + numNodes = uint64(4) + nodes = generateNodeAddresses(numNodes) + + defaultTimeout = Timeout{ + Initial: 2 * time.Second, + Delta: 200 * time.Millisecond, + } + ) + + // commonBroadcastCallback is the common method modification + // required for Broadcast, for all nodes + commonBroadcastCallback := func(broadcast *mockBroadcast) { + broadcast.broadcastProposeFn = func(message *types.ProposalMessage) { + broadcastProposeFn(message) + } + + broadcast.broadcastPrevoteFn = func(message *types.PrevoteMessage) { + broadcastPrevoteFn(message) + } + + broadcast.broadcastPrecommitFn = func(message *types.PrecommitMessage) { + broadcastPrecommitFn(message) + } + } + + // commonNodeCallback is the common method modification required + // for the Node, for all nodes + commonNodeCallback := func(node *mockNode, nodeIndex int) { + node.idFn = func() []byte { + return nodes[nodeIndex] + } + + node.hashFn = func(_ []byte) []byte { + return proposalHash + } + } + + // commonSignerCallback is the common method modification required + // for the Signer, for all nodes + commonSignerCallback := func(signer *mockSigner) { + signer.signFn = func(_ []byte) []byte { + return signature + } + + signer.isValidSignatureFn = func(_, sig []byte) bool { + return bytes.Equal(sig, signature) + } + } + + // commonVerifierCallback is the common method modification required + // for the Verifier, for all nodes + commonVerifierCallback := func(verifier *mockVerifier) { + verifier.isProposerFn = func(from []byte, _ uint64, _ uint64) bool { + return bytes.Equal(from, nodes[0]) + } + + verifier.isValidProposalFn = func(newProposal []byte, _ uint64) bool { + return bytes.Equal(newProposal, proposal) + } + + verifier.isValidatorFn = func(_ []byte) bool { + return true + } + + verifier.getTotalVotingPowerFn = func(_ uint64) uint64 { + return numNodes + } + + verifier.getSumVotingPowerFn = func(messages []Message) uint64 { + return uint64(len(messages)) + } + } + + var ( + verifierCallbackMap = map[int]verifierConfigCallback{ + 0: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 1: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 2: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 3: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + } + + nodeCallbackMap = map[int]nodeConfigCallback{ + 0: func(node *mockNode) { + commonNodeCallback(node, 0) + + node.buildProposalFn = func(_ uint64) []byte { + return proposal + } + }, + 1: func(node *mockNode) { + commonNodeCallback(node, 1) + }, + 2: func(node *mockNode) { + commonNodeCallback(node, 2) + }, + 3: func(node *mockNode) { + commonNodeCallback(node, 3) + }, + } + + broadcastCallbackMap = map[int]broadcastConfigCallback{ + 0: commonBroadcastCallback, + 1: commonBroadcastCallback, + 2: commonBroadcastCallback, + 3: commonBroadcastCallback, + } + + signerCallbackMap = map[int]signerConfigCallback{ + 0: commonSignerCallback, + 1: commonSignerCallback, + 2: commonSignerCallback, + 3: commonSignerCallback, + } + + commonOptions = []Option{ + WithProposeTimeout(defaultTimeout), + WithPrevoteTimeout(defaultTimeout), + WithPrecommitTimeout(defaultTimeout), + } + + optionsCallbackMap = map[int][]Option{ + 0: commonOptions, + 1: commonOptions, + 2: commonOptions, + 3: commonOptions, + } + ) + + // Create the mock cluster + cluster := newMockCluster( + numNodes, + verifierCallbackMap, + nodeCallbackMap, + broadcastCallbackMap, + signerCallbackMap, + optionsCallbackMap, + ) + + broadcastProposeFn = func(message *types.ProposalMessage) { + require.NoError(t, cluster.pushProposalMessage(message)) + } + + broadcastPrevoteFn = func(message *types.PrevoteMessage) { + require.NoError(t, cluster.pushPrevoteMessage(message)) + } + + broadcastPrecommitFn = func(message *types.PrecommitMessage) { + require.NoError(t, cluster.pushPrecommitMessage(message)) + } + + // Start the main run loops + cluster.runSequence(0) + + // Wait until the main run loops finish + cluster.ensureShutdown(5 * time.Second) + + // Make sure the finalized proposals match what node 0 proposed + for _, finalizedProposal := range cluster.finalizedProposals { + require.NotNil(t, finalizedProposal) + + assert.True(t, bytes.Equal(finalizedProposal.Data, proposal)) + assert.True(t, bytes.Equal(finalizedProposal.ID, proposalHash)) + } +} + +// TestConsensus_InvalidBlock tests the following scenario: +// N = 4 +// +// - Node 0 is the proposer for block 1, round 0 +// - Node 0 proposes an invalid block B +// - Other nodes should verify that the block is invalid +// - All nodes should move to round 1, and start a new consensus round +// - Node 1 is the proposer for block 1, round 1 +// - Node 1 proposes a valid block B' +// - All nodes go through the consensus states to insert the valid block B' +func TestConsensus_InvalidFlow(t *testing.T) { + t.Parallel() + + var ( + broadcastProposeFn func(message *types.ProposalMessage) + broadcastPrevoteFn func(message *types.PrevoteMessage) + broadcastPrecommitFn func(message *types.PrecommitMessage) + + proposals = [][]byte{ + []byte("proposal 1"), // proposed by node 0 + []byte("proposal 2"), // proposed by node 1 + } + + proposalHashes = [][]byte{ + []byte("proposal hash 1"), // for proposal 1 + []byte("proposal hash 2"), // for proposal 2 + } + + signature = []byte("signature") + numNodes = uint64(4) + nodes = generateNodeAddresses(numNodes) + + defaultTimeout = Timeout{ + Initial: 2 * time.Second, + Delta: 200 * time.Millisecond, + } + + precommitTimeout = Timeout{ + Initial: 300 * time.Millisecond, // low timeout, so a new round is started quicker + Delta: 200 * time.Millisecond, + } + ) + + // commonBroadcastCallback is the common method modification + // required for Broadcast, for all nodes + commonBroadcastCallback := func(broadcast *mockBroadcast) { + broadcast.broadcastProposeFn = func(message *types.ProposalMessage) { + broadcastProposeFn(message) + } + + broadcast.broadcastPrevoteFn = func(message *types.PrevoteMessage) { + broadcastPrevoteFn(message) + } + + broadcast.broadcastPrecommitFn = func(message *types.PrecommitMessage) { + broadcastPrecommitFn(message) + } + } + + // commonNodeCallback is the common method modification required + // for the Node, for all nodes + commonNodeCallback := func(node *mockNode, nodeIndex int) { + node.idFn = func() []byte { + return nodes[nodeIndex] + } + + node.hashFn = func(proposal []byte) []byte { + if bytes.Equal(proposal, proposals[0]) { + return proposalHashes[0] + } + + return proposalHashes[1] + } + } + + // commonSignerCallback is the common method modification required + // for the Signer, for all nodes + commonSignerCallback := func(signer *mockSigner) { + signer.signFn = func(_ []byte) []byte { + return signature + } + + signer.isValidSignatureFn = func(_, sig []byte) bool { + return bytes.Equal(sig, signature) + } + } + + // commonVerifierCallback is the common method modification required + // for the Verifier, for all nodes + commonVerifierCallback := func(verifier *mockVerifier) { + verifier.isProposerFn = func(from []byte, _ uint64, round uint64) bool { + // Node 0 is the proposer for round 0 + // Node 1 is the proposer for round 1 + return bytes.Equal(from, nodes[round]) + } + + verifier.isValidProposalFn = func(newProposal []byte, _ uint64) bool { + // Node 1 is the proposer for round 1, + // and their proposal is the only one that's valid + return bytes.Equal(newProposal, proposals[1]) + } + + verifier.isValidatorFn = func(_ []byte) bool { + return true + } + + verifier.getTotalVotingPowerFn = func(_ uint64) uint64 { + return numNodes + } + + verifier.getSumVotingPowerFn = func(messages []Message) uint64 { + return uint64(len(messages)) + } + } + + var ( + verifierCallbackMap = map[int]verifierConfigCallback{ + 0: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 1: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 2: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 3: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + } + + nodeCallbackMap = map[int]nodeConfigCallback{ + 0: func(node *mockNode) { + commonNodeCallback(node, 0) + + node.buildProposalFn = func(_ uint64) []byte { + return proposals[0] + } + }, + 1: func(node *mockNode) { + commonNodeCallback(node, 1) + + node.buildProposalFn = func(_ uint64) []byte { + return proposals[1] + } + }, + 2: func(node *mockNode) { + commonNodeCallback(node, 2) + }, + 3: func(node *mockNode) { + commonNodeCallback(node, 3) + }, + } + + broadcastCallbackMap = map[int]broadcastConfigCallback{ + 0: commonBroadcastCallback, + 1: commonBroadcastCallback, + 2: commonBroadcastCallback, + 3: commonBroadcastCallback, + } + + signerCallbackMap = map[int]signerConfigCallback{ + 0: commonSignerCallback, + 1: commonSignerCallback, + 2: commonSignerCallback, + 3: commonSignerCallback, + } + + commonOptions = []Option{ + WithProposeTimeout(defaultTimeout), + WithPrevoteTimeout(defaultTimeout), + WithPrecommitTimeout(precommitTimeout), + } + + optionsCallbackMap = map[int][]Option{ + 0: commonOptions, + 1: commonOptions, + 2: commonOptions, + 3: commonOptions, + } + ) + + // Create the mock cluster + cluster := newMockCluster( + numNodes, + verifierCallbackMap, + nodeCallbackMap, + broadcastCallbackMap, + signerCallbackMap, + optionsCallbackMap, + ) + + broadcastProposeFn = func(message *types.ProposalMessage) { + _ = cluster.pushProposalMessage(message) //nolint:errcheck // No need to check + } + + broadcastPrevoteFn = func(message *types.PrevoteMessage) { + _ = cluster.pushPrevoteMessage(message) //nolint:errcheck // No need to check + } + + broadcastPrecommitFn = func(message *types.PrecommitMessage) { + _ = cluster.pushPrecommitMessage(message) //nolint:errcheck // No need to check + } + + // Start the main run loops + cluster.runSequence(0) + + // Wait until the main run loops finish + cluster.ensureShutdown(5 * time.Second) + + // Make sure the nodes switched to the new round + assert.True(t, cluster.areAllNodesOnRound(1)) + + // Make sure the finalized proposals match what node 0 proposed + for _, finalizedProposal := range cluster.finalizedProposals { + require.NotNil(t, finalizedProposal) + + assert.True(t, bytes.Equal(finalizedProposal.Data, proposals[1])) + assert.True(t, bytes.Equal(finalizedProposal.ID, proposalHashes[1])) + } +} diff --git a/tm2/pkg/libtm/core/messages.go b/tm2/pkg/libtm/core/messages.go new file mode 100644 index 00000000000..961e8586c8d --- /dev/null +++ b/tm2/pkg/libtm/core/messages.go @@ -0,0 +1,97 @@ +package core + +import ( + "errors" + "fmt" + + "github.com/gnolang/libtm/messages/types" +) + +var ( + ErrInvalidMessageSignature = errors.New("invalid message signature") + ErrMessageFromNonValidator = errors.New("message is from a non-validator") + ErrEarlierHeightMessage = errors.New("message is for an earlier height") + ErrEarlierRoundMessage = errors.New("message is for an earlier round") +) + +// AddProposalMessage verifies and adds a new proposal message to the consensus engine +func (t *Tendermint) AddProposalMessage(message *types.ProposalMessage) error { + // Verify the incoming message + if err := t.verifyMessage(message); err != nil { + return fmt.Errorf("unable to verify proposal message, %w", err) + } + + // Add the message to the store + t.store.addProposalMessage(message) + + return nil +} + +// AddPrevoteMessage verifies and adds a new prevote message to the consensus engine +func (t *Tendermint) AddPrevoteMessage(message *types.PrevoteMessage) error { + // Verify the incoming message + if err := t.verifyMessage(message); err != nil { + return fmt.Errorf("unable to verify prevote message, %w", err) + } + + // Add the message to the store + t.store.addPrevoteMessage(message) + + return nil +} + +// AddPrecommitMessage verifies and adds a new precommit message to the consensus engine +func (t *Tendermint) AddPrecommitMessage(message *types.PrecommitMessage) error { + // Verify the incoming message + if err := t.verifyMessage(message); err != nil { + return fmt.Errorf("unable to verify precommit message, %w", err) + } + + // Add the message to the store + t.store.addPrecommitMessage(message) + + return nil +} + +// verifyMessage is the common base message verification +func (t *Tendermint) verifyMessage(message Message) error { + // Check if the message is valid + if err := message.Verify(); err != nil { + return fmt.Errorf("unable to verify message, %w", err) + } + + // Make sure the message sender is a validator + if !t.verifier.IsValidator(message.GetSender()) { + return ErrMessageFromNonValidator + } + + // Get the signature payload + signPayload := message.GetSignaturePayload() + + // Make sure the signature is valid + if !t.signer.IsValidSignature(signPayload, message.GetSignature()) { + return ErrInvalidMessageSignature + } + + // Make sure the message view is valid + var ( + view = message.GetView() + + currentHeight = t.state.getHeight() + currentRound = t.state.getRound() + ) + + // Make sure the height is valid. + // The message height needs to be the current state height, or greater + if currentHeight > view.GetHeight() { + return ErrEarlierHeightMessage + } + + // Make sure the round is valid. + // The message rounds needs to be >= the current round + if currentRound > view.GetRound() { + return ErrEarlierRoundMessage + } + + return nil +} diff --git a/tm2/pkg/libtm/core/mocks_test.go b/tm2/pkg/libtm/core/mocks_test.go new file mode 100644 index 00000000000..174743f31cf --- /dev/null +++ b/tm2/pkg/libtm/core/mocks_test.go @@ -0,0 +1,446 @@ +package core + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "github.com/gnolang/libtm/messages/types" +) + +type ( + broadcastProposeDelegate func(*types.ProposalMessage) + broadcastPrevoteDelegate func(*types.PrevoteMessage) + broadcastPrecommitDelegate func(*types.PrecommitMessage) +) + +type mockBroadcast struct { + broadcastProposeFn broadcastProposeDelegate + broadcastPrevoteFn broadcastPrevoteDelegate + broadcastPrecommitFn broadcastPrecommitDelegate +} + +func (m *mockBroadcast) BroadcastPropose(message *types.ProposalMessage) { + if m.broadcastProposeFn != nil { + m.broadcastProposeFn(message) + } +} + +func (m *mockBroadcast) BroadcastPrevote(message *types.PrevoteMessage) { + if m.broadcastPrevoteFn != nil { + m.broadcastPrevoteFn(message) + } +} + +func (m *mockBroadcast) BroadcastPrecommit(message *types.PrecommitMessage) { + if m.broadcastPrecommitFn != nil { + m.broadcastPrecommitFn(message) + } +} + +type ( + idDelegate func() []byte + hashDelegate func([]byte) []byte + buildProposalDelegate func(uint64) []byte +) + +type mockNode struct { + idFn idDelegate + hashFn hashDelegate + buildProposalFn buildProposalDelegate +} + +func (m *mockNode) ID() []byte { + if m.idFn != nil { + return m.idFn() + } + + return nil +} + +func (m *mockNode) Hash(proposal []byte) []byte { + if m.hashFn != nil { + return m.hashFn(proposal) + } + + return nil +} + +func (m *mockNode) BuildProposal(height uint64) []byte { + if m.buildProposalFn != nil { + return m.buildProposalFn(height) + } + + return nil +} + +type ( + signDelegate func([]byte) []byte + isValidSignatureDelegate func([]byte, []byte) bool +) + +type mockSigner struct { + signFn signDelegate + isValidSignatureFn isValidSignatureDelegate +} + +func (m *mockSigner) Sign(data []byte) []byte { + if m.signFn != nil { + return m.signFn(data) + } + + return nil +} + +func (m *mockSigner) IsValidSignature(data, signature []byte) bool { + if m.isValidSignatureFn != nil { + return m.isValidSignatureFn(data, signature) + } + + return false +} + +type ( + isProposerDelegate func([]byte, uint64, uint64) bool + isValidatorDelegate func([]byte) bool + isValidProposalDelegate func([]byte, uint64) bool + getTotalVotingPowerDelegate func(uint64) uint64 + getSumVotingPowerDelegate func([]Message) uint64 +) + +type mockVerifier struct { + isProposerFn isProposerDelegate + isValidatorFn isValidatorDelegate + isValidProposalFn isValidProposalDelegate + getTotalVotingPowerFn getTotalVotingPowerDelegate + getSumVotingPowerFn getSumVotingPowerDelegate +} + +func (m *mockVerifier) GetTotalVotingPower(height uint64) uint64 { + if m.getTotalVotingPowerFn != nil { + return m.getTotalVotingPowerFn(height) + } + + return 0 +} + +func (m *mockVerifier) GetSumVotingPower(msgs []Message) uint64 { + if m.getSumVotingPowerFn != nil { + return m.getSumVotingPowerFn(msgs) + } + + return 0 +} + +func (m *mockVerifier) IsProposer(id []byte, height, round uint64) bool { + if m.isProposerFn != nil { + return m.isProposerFn(id, height, round) + } + + return false +} + +func (m *mockVerifier) IsValidator(from []byte) bool { + if m.isValidatorFn != nil { + return m.isValidatorFn(from) + } + + return false +} + +func (m *mockVerifier) IsValidProposal(proposal []byte, height uint64) bool { + if m.isValidProposalFn != nil { + return m.isValidProposalFn(proposal, height) + } + + return false +} + +type ( + getViewDelegate func() *types.View + getSenderDelegate func() []byte + getSignatureDelegate func() []byte + getSignaturePayloadDelegate func() []byte + verifyDelegate func() error +) + +type mockMessage struct { + getViewFn getViewDelegate + getSenderFn getSenderDelegate + getSignatureFn getSignatureDelegate + getSignaturePayloadFn getSignaturePayloadDelegate + verifyFn verifyDelegate +} + +func (m *mockMessage) GetView() *types.View { + if m.getViewFn != nil { + return m.getViewFn() + } + + return nil +} + +func (m *mockMessage) GetSender() []byte { + if m.getSenderFn != nil { + return m.getSenderFn() + } + + return nil +} + +func (m *mockMessage) GetSignature() []byte { + if m.getSignatureFn != nil { + return m.getSignatureFn() + } + + return nil +} + +func (m *mockMessage) GetSignaturePayload() []byte { + if m.getSignaturePayloadFn != nil { + return m.getSignaturePayloadFn() + } + + return nil +} + +func (m *mockMessage) Verify() error { + if m.verifyFn != nil { + return m.verifyFn() + } + + return nil +} + +// mockNodeContext keeps track of the node runtime context +type mockNodeContext struct { + ctx context.Context + cancelFn context.CancelFunc +} + +// mockNodeWg is the WaitGroup wrapper for the cluster nodes +type mockNodeWg struct { + sync.WaitGroup + count int64 +} + +func (wg *mockNodeWg) Add(delta int) { + wg.WaitGroup.Add(delta) +} + +func (wg *mockNodeWg) Done() { + wg.WaitGroup.Done() + atomic.AddInt64(&wg.count, 1) +} + +func (wg *mockNodeWg) getDone() int64 { + return atomic.LoadInt64(&wg.count) +} + +func (wg *mockNodeWg) resetDone() { + atomic.StoreInt64(&wg.count, 0) +} + +type ( + verifierConfigCallback func(*mockVerifier) + nodeConfigCallback func(*mockNode) + broadcastConfigCallback func(*mockBroadcast) + signerConfigCallback func(*mockSigner) +) + +// mockCluster represents a mock Tendermint cluster +type mockCluster struct { + nodes []*Tendermint // references to the nodes in the cluster + ctxs []mockNodeContext // context handlers for the nodes in the cluster + finalizedProposals []*FinalizedProposal // finalized proposals for the nodes + + stoppedWg mockNodeWg +} + +// newMockCluster creates a new mock Tendermint cluster +func newMockCluster( + count uint64, + verifierCallbackMap map[int]verifierConfigCallback, + nodeCallbackMap map[int]nodeConfigCallback, + broadcastCallbackMap map[int]broadcastConfigCallback, + signerCallbackMap map[int]signerConfigCallback, + optionsMap map[int][]Option, +) *mockCluster { + if count < 1 { + return nil + } + + nodes := make([]*Tendermint, count) + nodeCtxs := make([]mockNodeContext, count) + + for index := 0; index < int(count); index++ { + var ( + verifier = &mockVerifier{} + node = &mockNode{} + broadcast = &mockBroadcast{} + signer = &mockSigner{} + options = make([]Option, 0) + ) + + // Execute set callbacks, if any + if verifierCallbackMap != nil { + if verifierCallback, isSet := verifierCallbackMap[index]; isSet { + verifierCallback(verifier) + } + } + + if nodeCallbackMap != nil { + if nodeCallback, isSet := nodeCallbackMap[index]; isSet { + nodeCallback(node) + } + } + + if broadcastCallbackMap != nil { + if broadcastCallback, isSet := broadcastCallbackMap[index]; isSet { + broadcastCallback(broadcast) + } + } + + if signerCallbackMap != nil { + if signerCallback, isSet := signerCallbackMap[index]; isSet { + signerCallback(signer) + } + } + + if optionsMap != nil { + if opts, isSet := optionsMap[index]; isSet { + options = opts + } + } + + // Create a new instance of the Tendermint node + nodes[index] = NewTendermint( + verifier, + node, + broadcast, + signer, + options..., + ) + + // Instantiate context for the nodes + ctx, cancelFn := context.WithCancel(context.Background()) + nodeCtxs[index] = mockNodeContext{ + ctx: ctx, + cancelFn: cancelFn, + } + } + + return &mockCluster{ + nodes: nodes, + ctxs: nodeCtxs, + finalizedProposals: make([]*FinalizedProposal, count), + } +} + +// runSequence runs the cluster sequence for the given height +func (m *mockCluster) runSequence(height uint64) { + m.stoppedWg.resetDone() + + for nodeIndex, node := range m.nodes { + m.stoppedWg.Add(1) + + go func( + ctx context.Context, + node *Tendermint, + nodeIndex int, + height uint64, + ) { + defer m.stoppedWg.Done() + + // Start the main run loop for the node + finalizedProposal := node.RunSequence(ctx, height) + + m.finalizedProposals[nodeIndex] = finalizedProposal + }(m.ctxs[nodeIndex].ctx, node, nodeIndex, height) + } +} + +// awaitCompletion waits for completion of all +// nodes in the cluster +func (m *mockCluster) awaitCompletion() { + // Wait for all main run loops to signalize + // that they're finished + m.stoppedWg.Wait() +} + +// ensureShutdown ensures the cluster is shutdown within the given duration +func (m *mockCluster) ensureShutdown(timeout time.Duration) { + ch := time.After(timeout) + + for { + select { + case <-ch: + m.forceShutdown() + + return + default: + if m.stoppedWg.getDone() == int64(len(m.nodes)) { + // All nodes are finished + return + } + } + } +} + +// forceShutdown sends a stop signal to all running nodes +// in the cluster, and awaits their completion +func (m *mockCluster) forceShutdown() { + // Send a stop signal to all the nodes + for _, ctx := range m.ctxs { + ctx.cancelFn() + } + + // Wait for all the nodes to finish + m.awaitCompletion() +} + +// pushProposalMessage relays the proposal message to all nodes in the cluster +func (m *mockCluster) pushProposalMessage(message *types.ProposalMessage) error { + for _, node := range m.nodes { + if err := node.AddProposalMessage(message); err != nil { + return err + } + } + + return nil +} + +// pushPrevoteMessage relays the prevote message to all nodes in the cluster +func (m *mockCluster) pushPrevoteMessage(message *types.PrevoteMessage) error { + for _, node := range m.nodes { + if err := node.AddPrevoteMessage(message); err != nil { + return err + } + } + + return nil +} + +// pushPrecommitMessage relays the precommit message to all nodes in the cluster +func (m *mockCluster) pushPrecommitMessage(message *types.PrecommitMessage) error { + for _, node := range m.nodes { + if err := node.AddPrecommitMessage(message); err != nil { + return err + } + } + + return nil +} + +// areAllNodesOnRound checks to make sure all nodes +// are on the same specified round +func (m *mockCluster) areAllNodesOnRound(round uint64) bool { + for _, node := range m.nodes { + if node.state.getRound() != round { + return false + } + } + + return true +} diff --git a/tm2/pkg/libtm/core/options.go b/tm2/pkg/libtm/core/options.go new file mode 100644 index 00000000000..9ff8581ada8 --- /dev/null +++ b/tm2/pkg/libtm/core/options.go @@ -0,0 +1,33 @@ +package core + +import "log/slog" + +type Option func(t *Tendermint) + +// WithLogger specifies the logger for the Tendermint consensus engine +func WithLogger(l *slog.Logger) Option { + return func(t *Tendermint) { + t.logger = l + } +} + +// WithProposeTimeout specifies the propose state timeout +func WithProposeTimeout(timeout Timeout) Option { + return func(t *Tendermint) { + t.timeouts[propose] = timeout + } +} + +// WithPrevoteTimeout specifies the prevote state timeout +func WithPrevoteTimeout(timeout Timeout) Option { + return func(t *Tendermint) { + t.timeouts[prevote] = timeout + } +} + +// WithPrecommitTimeout specifies the precommit state timeout +func WithPrecommitTimeout(timeout Timeout) Option { + return func(t *Tendermint) { + t.timeouts[precommit] = timeout + } +} diff --git a/tm2/pkg/libtm/core/options_test.go b/tm2/pkg/libtm/core/options_test.go new file mode 100644 index 00000000000..9d7909985fe --- /dev/null +++ b/tm2/pkg/libtm/core/options_test.go @@ -0,0 +1,87 @@ +package core + +import ( + "io" + "log/slog" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewTendermint_Options(t *testing.T) { + t.Parallel() + + t.Run("Withlogger", func(t *testing.T) { + t.Parallel() + + l := slog.New(slog.NewTextHandler(io.Discard, nil)) + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithLogger(l), + ) + + assert.Equal(t, tm.logger, l) + }) + + t.Run("WithProposeTimeout", func(t *testing.T) { + t.Parallel() + + timeout := Timeout{ + Initial: 500 * time.Millisecond, + Delta: 0, + } + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithProposeTimeout(timeout), + ) + + assert.Equal(t, tm.timeouts[propose], timeout) + }) + + t.Run("WithPrevoteTimeout", func(t *testing.T) { + t.Parallel() + + timeout := Timeout{ + Initial: 500 * time.Millisecond, + Delta: 0, + } + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithPrevoteTimeout(timeout), + ) + + assert.Equal(t, tm.timeouts[prevote], timeout) + }) + + t.Run("WithPrecommitTimeout", func(t *testing.T) { + t.Parallel() + + timeout := Timeout{ + Initial: 500 * time.Millisecond, + Delta: 0, + } + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithPrecommitTimeout(timeout), + ) + + assert.Equal(t, tm.timeouts[precommit], timeout) + }) +} diff --git a/tm2/pkg/libtm/core/quorum.go b/tm2/pkg/libtm/core/quorum.go new file mode 100644 index 00000000000..cf12bc9b8f5 --- /dev/null +++ b/tm2/pkg/libtm/core/quorum.go @@ -0,0 +1,23 @@ +package core + +// hasSuperMajority verifies that there is a 2F+1 voting power majority +// in the given message set. +// This follows the constraint that N > 3F, i.e., the total voting power of faulty processes is smaller than +// one third of the total voting power +func (t *Tendermint) hasSuperMajority(messages []Message) bool { + sumVotingPower := t.verifier.GetSumVotingPower(messages) + totalVotingPower := t.verifier.GetTotalVotingPower(t.state.getHeight()) + + return sumVotingPower > (2 * totalVotingPower / 3) +} + +// hasFaultyMajority verifies that there is an F+1 voting power majority +// in the given message set. +// This follows the constraint that N > 3F, i.e., the total voting power of faulty processes is smaller than +// one third of the total voting power +func (t *Tendermint) hasFaultyMajority(messages []Message) bool { + sumVotingPower := t.verifier.GetSumVotingPower(messages) + totalVotingPower := t.verifier.GetTotalVotingPower(t.state.getHeight()) + + return sumVotingPower > totalVotingPower/3 +} diff --git a/tm2/pkg/libtm/core/quorum_test.go b/tm2/pkg/libtm/core/quorum_test.go new file mode 100644 index 00000000000..23f804de2b7 --- /dev/null +++ b/tm2/pkg/libtm/core/quorum_test.go @@ -0,0 +1,219 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTendermint_QuorumSuperMajority(t *testing.T) { + t.Parallel() + + var ( + // Equal voting power map + votingPowerMap = map[string]uint64{ + "1": 1, + "2": 1, + "3": 1, + "4": 1, + } + + mockMessages = []*mockMessage{ + { + getSenderFn: func() []byte { + return []byte("1") + }, + }, + { + getSenderFn: func() []byte { + return []byte("2") + }, + }, + { + getSenderFn: func() []byte { + return []byte("3") + }, + }, + { + getSenderFn: func() []byte { + return []byte("4") + }, + }, + } + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(_ uint64) uint64 { + return uint64(len(votingPowerMap)) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + sum := uint64(0) + + for _, message := range messages { + sum += votingPowerMap[string(message.GetSender())] + } + + return sum + }, + } + ) + + testTable := []struct { + name string + messages []*mockMessage + shouldHaveMajority bool + }{ + { + "4/4 validators", + mockMessages, + true, + }, + { + "3/4 validators", + mockMessages[1:], + true, + }, + { + "2/4 validators", + mockMessages[2:], + false, + }, + { + "1/4 validators", + mockMessages[:1], + false, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + tm := NewTendermint( + mockVerifier, + nil, + nil, + nil, + ) + + convertedMessages := make([]Message, 0, len(testCase.messages)) + + for _, mockMessage := range testCase.messages { + convertedMessages = append(convertedMessages, mockMessage) + } + + assert.Equal( + t, + testCase.shouldHaveMajority, + tm.hasSuperMajority(convertedMessages), + ) + }) + } +} + +func TestTendermint_QuorumFaultyMajority(t *testing.T) { + t.Parallel() + + var ( + // Equal voting power map + votingPowerMap = map[string]uint64{ + "1": 1, + "2": 1, + "3": 1, + "4": 1, + } + + mockMessages = []*mockMessage{ + { + getSenderFn: func() []byte { + return []byte("1") + }, + }, + { + getSenderFn: func() []byte { + return []byte("2") + }, + }, + { + getSenderFn: func() []byte { + return []byte("3") + }, + }, + { + getSenderFn: func() []byte { + return []byte("4") + }, + }, + } + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(_ uint64) uint64 { + return uint64(len(votingPowerMap)) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + sum := uint64(0) + + for _, message := range messages { + sum += votingPowerMap[string(message.GetSender())] + } + + return sum + }, + } + ) + + testTable := []struct { + name string + messages []*mockMessage + shouldHaveMajority bool + }{ + { + "4/4 validators", + mockMessages, + true, + }, + { + "3/4 validators", + mockMessages[1:], + true, + }, + { + "2/4 validators", + mockMessages[2:], + true, + }, + { + "1/4 validators", + mockMessages[:1], + false, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + tm := NewTendermint( + mockVerifier, + nil, + nil, + nil, + ) + + convertedMessages := make([]Message, 0, len(testCase.messages)) + + for _, mockMessage := range testCase.messages { + convertedMessages = append(convertedMessages, mockMessage) + } + + assert.Equal( + t, + testCase.shouldHaveMajority, + tm.hasFaultyMajority(convertedMessages), + ) + }) + } +} diff --git a/tm2/pkg/libtm/core/state.go b/tm2/pkg/libtm/core/state.go new file mode 100644 index 00000000000..6713a411e4d --- /dev/null +++ b/tm2/pkg/libtm/core/state.go @@ -0,0 +1,84 @@ +package core + +import ( + "sync/atomic" + + "github.com/gnolang/libtm/messages/types" +) + +// step is the current state step +type step uint32 + +const ( + propose step = iota + prevote + precommit +) + +// set updates the current step value [THREAD SAFE] +func (s *step) set(n step) { + atomic.SwapUint32((*uint32)(s), uint32(n)) +} + +// get fetches the current step value [THREAD SAFE] +func (s *step) get() step { + return step(atomic.LoadUint32((*uint32)(s))) +} + +// state holds information about the current consensus state +type state struct { + view *types.View + + acceptedProposal []byte + acceptedProposalID []byte + + lockedValue []byte + validValue []byte + + lockedRound int64 + validRound int64 + + step step +} + +// newState creates a fresh state using the given view +func newState() state { + return state{ + view: &types.View{ + Height: 0, // zero height + Round: 0, // zero round + }, + step: propose, + acceptedProposal: nil, + acceptedProposalID: nil, + lockedValue: nil, + lockedRound: -1, + validValue: nil, + validRound: -1, + } +} + +// getHeight fetches the current view height [THREAD SAFE] +func (s *state) getHeight() uint64 { + return atomic.LoadUint64(&s.view.Height) +} + +// getRound fetches the current view round [THREAD SAFE] +func (s *state) getRound() uint64 { + return atomic.LoadUint64(&s.view.Round) +} + +// increaseRound increases the current view round by 1 [THREAD SAFE] +func (s *state) increaseRound() { + atomic.AddUint64(&s.view.Round, 1) +} + +// setRound sets the current view round to the given value [THREAD SAFE] +func (s *state) setRound(r uint64) { + atomic.SwapUint64(&s.view.Round, r) +} + +// setHeight sets the current view height to the given value [THREAD SAFE] +func (s *state) setHeight(h uint64) { + atomic.SwapUint64(&s.view.Height, h) +} diff --git a/tm2/pkg/libtm/core/store.go b/tm2/pkg/libtm/core/store.go new file mode 100644 index 00000000000..395dd2d023e --- /dev/null +++ b/tm2/pkg/libtm/core/store.go @@ -0,0 +1,65 @@ +package core + +import ( + "github.com/gnolang/libtm/messages" + "github.com/gnolang/libtm/messages/types" +) + +// store is the message store +type store struct { + proposeMessages *messages.Collector[types.ProposalMessage] + prevoteMessages *messages.Collector[types.PrevoteMessage] + precommitMessages *messages.Collector[types.PrecommitMessage] +} + +// newStore creates a new message store +func newStore() store { + return store{ + proposeMessages: messages.NewCollector[types.ProposalMessage](), + prevoteMessages: messages.NewCollector[types.PrevoteMessage](), + precommitMessages: messages.NewCollector[types.PrecommitMessage](), + } +} + +// addProposalMessage adds a proposal message to the store +func (s *store) addProposalMessage(proposal *types.ProposalMessage) { + s.proposeMessages.AddMessage(proposal.View, proposal.Sender, proposal) +} + +// addPrevoteMessage adds a prevote message to the store +func (s *store) addPrevoteMessage(prevote *types.PrevoteMessage) { + s.prevoteMessages.AddMessage(prevote.View, prevote.Sender, prevote) +} + +// addPrecommitMessage adds a precommit message to the store +func (s *store) addPrecommitMessage(precommit *types.PrecommitMessage) { + s.precommitMessages.AddMessage(precommit.View, precommit.Sender, precommit) +} + +// subscribeToPropose subscribes to incoming PROPOSE messages +func (s *store) subscribeToPropose() (<-chan func() []*types.ProposalMessage, func()) { + return s.proposeMessages.Subscribe() +} + +// subscribeToPrevote subscribes to incoming PREVOTE messages +func (s *store) subscribeToPrevote() (<-chan func() []*types.PrevoteMessage, func()) { + return s.prevoteMessages.Subscribe() +} + +// subscribeToPrecommit subscribes to incoming PRECOMMIT messages +func (s *store) subscribeToPrecommit() (<-chan func() []*types.PrecommitMessage, func()) { + return s.precommitMessages.Subscribe() +} + +// dropMessages drops all messages from the store that are +// less than the given view (earlier) +func (s *store) dropMessages(view *types.View) { + // Clean up the propose messages + s.proposeMessages.DropMessages(view) + + // Clean up the prevote messages + s.prevoteMessages.DropMessages(view) + + // Clean up the precommit messages + s.precommitMessages.DropMessages(view) +} diff --git a/tm2/pkg/libtm/core/tendermint.go b/tm2/pkg/libtm/core/tendermint.go new file mode 100644 index 00000000000..46b610b6bee --- /dev/null +++ b/tm2/pkg/libtm/core/tendermint.go @@ -0,0 +1,907 @@ +package core + +import ( + "bytes" + "context" + "io" + "log/slog" + "sync" + + "github.com/gnolang/libtm/messages" + + "github.com/gnolang/libtm/messages/types" +) + +// Tendermint is the single consensus engine instance +type Tendermint struct { + // store is the message store + store store + + verifier Verifier + node Node + broadcast Broadcast + signer Signer + + // logger is the consensus engine logger + logger *slog.Logger + + // timeouts hold state timeout information (constant) + timeouts map[step]Timeout + + // state is the current Tendermint consensus state + state state + + // wg is the barrier for keeping all + // parallel consensus processes synced + wg sync.WaitGroup +} + +// FinalizedProposal is the finalized proposal wrapper, that +// contains the raw proposal data, and the ID of the data (usually hash) +type FinalizedProposal struct { + Data []byte // the raw proposal data, accepted proposal + ID []byte // the ID of the proposal (usually hash) +} + +// newFinalizedProposal creates a new finalized proposal wrapper +func newFinalizedProposal(data, id []byte) *FinalizedProposal { + return &FinalizedProposal{ + Data: data, + ID: id, + } +} + +// NewTendermint creates a new instance of the Tendermint consensus engine +func NewTendermint( + verifier Verifier, + node Node, + broadcast Broadcast, + signer Signer, + opts ...Option, +) *Tendermint { + t := &Tendermint{ + state: newState(), + store: newStore(), + verifier: verifier, + node: node, + broadcast: broadcast, + signer: signer, + logger: slog.New(slog.NewTextHandler(io.Discard, nil)), + timeouts: getDefaultTimeoutMap(), + } + + // Apply any options + for _, opt := range opts { + opt(t) + } + + return t +} + +// RunSequence runs the Tendermint consensus sequence for a given height, +// returning only when a proposal has been finalized (consensus reached), or +// the context has been cancelled +func (t *Tendermint) RunSequence(ctx context.Context, h uint64) *FinalizedProposal { + t.logger.Debug( + "RunSequence", + slog.Uint64("height", h), + slog.String("node", string(t.node.ID())), + ) + + // Initialize the state before starting the sequence + t.state.setHeight(h) + + // Grab the process view + view := &types.View{ + Height: h, + Round: t.state.getRound(), + } + + // Drop all old messages + t.store.dropMessages(view) + + for { + // set up the round context + ctxRound, cancelRound := context.WithCancel(ctx) + teardown := func() { + cancelRound() + t.wg.Wait() + } + + select { + case proposal := <-t.finalizeProposal(ctxRound): + teardown() + + // Check if the proposal has been finalized + if proposal != nil { + t.logger.Info( + "RunSequence: proposal finalized", + slog.Uint64("height", h), + slog.String("node", string(t.node.ID())), + ) + + return proposal + } + + t.logger.Info( + "RunSequence round expired", + slog.Uint64("height", h), + slog.Uint64("round", t.state.getRound()), + slog.String("node", string(t.node.ID())), + ) + + // 65: Function OnTimeoutPrecommit(height, round) : + // 66: if height = hP ∧ round = roundP then + // 67: StartRound(roundP + 1) + t.state.increaseRound() + t.state.step.set(propose) + case recvRound := <-t.watchForRoundJumps(ctxRound): + teardown() + + t.logger.Info( + "RunSequence: round jump", + slog.Uint64("height", h), + slog.Uint64("from", t.state.getRound()), + slog.Uint64("to", recvRound), + slog.String("node", string(t.node.ID())), + ) + + t.state.setRound(recvRound) + t.state.step.set(propose) + case <-ctx.Done(): + teardown() + + t.logger.Info( + "RunSequence: context done", + slog.Uint64("height", h), + slog.Uint64("round", t.state.getRound()), + slog.String("node", string(t.node.ID())), + ) + + return nil + } + } +} + +// watchForRoundJumps monitors for F+1 (any) messages from a future round, and +// triggers the round switch context (channel) accordingly +func (t *Tendermint) watchForRoundJumps(ctx context.Context) <-chan uint64 { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + ch = make(chan uint64, 1) + ) + + // Signals the round jump to the given channel + signalRoundJump := func(round uint64) { + select { + case <-ctx.Done(): + case ch <- round: + } + } + + t.wg.Add(1) + + go func() { + defer t.wg.Done() + + var ( + proposeCh, unsubscribeProposeFn = t.store.subscribeToPropose() + prevoteCh, unsubscribePrevoteFn = t.store.subscribeToPrevote() + precommitCh, unsubscribePrecommitFn = t.store.subscribeToPrecommit() + ) + + defer func() { + unsubscribeProposeFn() + unsubscribePrevoteFn() + unsubscribePrecommitFn() + }() + + var ( + isValidProposeFn = func(m *types.ProposalMessage) bool { + view := m.GetView() + + return view.GetRound() > round && view.GetHeight() == height + } + isValidPrevoteFn = func(m *types.PrevoteMessage) bool { + view := m.GetView() + + return view.GetRound() > round && view.GetHeight() == height + } + isValidPrecommitFn = func(m *types.PrecommitMessage) bool { + view := m.GetView() + + return view.GetRound() > round && view.GetHeight() == height + } + ) + + var ( + proposeCache = newMessageCache[*types.ProposalMessage](isValidProposeFn) + prevoteCache = newMessageCache[*types.PrevoteMessage](isValidPrevoteFn) + precommitCache = newMessageCache[*types.PrecommitMessage](isValidPrecommitFn) + ) + + generateRoundMap := func(messages ...[]Message) map[uint64][]Message { + combined := make([]Message, 0) + for _, message := range messages { + combined = append(combined, message...) + } + + // Group messages by round + roundMap := make(map[uint64][]Message) + + for _, message := range combined { + messageRound := message.GetView().GetRound() + roundMap[messageRound] = append(roundMap[messageRound], message) + } + + return roundMap + } + + for { + select { + case <-ctx.Done(): + close(ch) + + return + case getProposeFn := <-proposeCh: + proposeCache.addMessages(getProposeFn()) + case getPrevoteFn := <-prevoteCh: + prevoteCache.addMessages(getPrevoteFn()) + case getPrecommitFn := <-precommitCh: + precommitCache.addMessages(getPrecommitFn()) + } + + var ( + proposeMessages = proposeCache.getMessages() + prevoteMessages = prevoteCache.getMessages() + precommitMessages = precommitCache.getMessages() + ) + + var ( + convertedPropose = make([]Message, 0, len(proposeMessages)) + convertedPrevote = make([]Message, 0, len(prevoteMessages)) + convertedPrecommit = make([]Message, 0, len(precommitMessages)) + ) + + messages.ConvertToInterface(proposeMessages, func(m *types.ProposalMessage) { + convertedPropose = append(convertedPropose, m) + }) + + messages.ConvertToInterface(prevoteMessages, func(m *types.PrevoteMessage) { + convertedPrevote = append(convertedPrevote, m) + }) + + messages.ConvertToInterface(precommitMessages, func(m *types.PrecommitMessage) { + convertedPrecommit = append(convertedPrecommit, m) + }) + + // Generate the round map + roundMap := generateRoundMap( + convertedPropose, + convertedPrevote, + convertedPrecommit, + ) + + // Find the highest round that satisfies an F+1 voting power majority. + // This max round will always need to be > 0 + maxRound := uint64(0) + + for messageRound, roundMessages := range roundMap { + if !t.hasFaultyMajority(roundMessages) { + continue + } + + if messageRound > maxRound { + maxRound = messageRound + } + } + + // Make sure the max round that has a faulty majority + // is actually greater than the process round + if maxRound > round { + signalRoundJump(maxRound) + + return + } + } + }() + + return ch +} + +// finalizeProposal starts the proposal finalization sequence +func (t *Tendermint) finalizeProposal(ctx context.Context) <-chan *FinalizedProposal { + ch := make(chan *FinalizedProposal, 1) + + t.wg.Add(1) + + go func() { + defer func() { + close(ch) + t.wg.Done() + }() + + // Run the consensus state machine, and save the finalized proposal (if any) + if finalizedProposal := t.runStates(ctx); finalizedProposal != nil { + ch <- finalizedProposal + } + }() + + return ch +} + +// startRound starts the consensus round. +// It is a required middle step (proposal evaluation) before +// the state machine is in full swing and +// the runs the same flow for everyone (proposer / non-proposers) +func (t *Tendermint) startRound(height, round uint64) { + // 14: if proposer(hp, roundP) = p then + // + // The proposal value can either be: + // - an old (valid / locked) proposal from a previous round + // - a completely new proposal (built from scratch) + // + // 15: if validValueP != nil then + // 16: proposal ← validValueP + var ( + proposal = t.state.validValue + proposalRound = t.state.validRound + ) + + // Check if a new proposal needs to be built + if proposal == nil { + t.logger.Info( + "building a proposal", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // No previous valid value present, + // build a new proposal + // + // 17: else + // 18: proposal ← getValue() + proposal = t.node.BuildProposal(height) + } + + // Build the propose message + var ( + proposeMessage = t.buildProposalMessage(proposal, proposalRound) + id = t.node.Hash(proposal) + ) + + // Broadcast the proposal to other consensus nodes + // + // 19: broadcast + t.broadcast.BroadcastPropose(proposeMessage) + + // Save the accepted proposal in the state. + // NOTE: This is different from validValue / lockedValue, + // since they require a 2F+1 quorum of specific messages + // in order to be set, whereas this is simply a reference + // value for different states (prevote, precommit) + t.state.acceptedProposal = proposal + t.state.acceptedProposalID = id + + // Build and broadcast the prevote message + // + // 24/30: broadcast + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(id)) + + // Since the current process is the proposer, + // it can directly move to the prevote state + // 27/33: stepP ← prevote + t.state.step.set(prevote) +} + +// runStates runs the consensus states, depending on the current step +func (t *Tendermint) runStates(ctx context.Context) *FinalizedProposal { + for { + currentStep := t.state.step.get() + + select { + case <-ctx.Done(): + return nil + default: + switch currentStep { + case propose: + t.runPropose(ctx) + case prevote: + t.runPrevote(ctx) + case precommit: + return t.runPrecommit(ctx) + } + } + } +} + +// runPropose runs the propose state in which the process +// waits for a valid PROPOSE message. +// This state handles the following situations: +// +// - The proposer for view (hP, roundP) has proposed a value with a proposal round -1 (first ever proposal for height) +// 22: upon from proposer(hP, roundP) while stepP = propose do +// 23: if valid(v) ∧ (lockedRoundP = −1 ∨ lockedValueP = v) then +// 24: broadcast +// 25: else +// 26: broadcast +// 27: stepP ← prevote +// +// - The proposer for view (hP, roundP) has proposed a value that was accepted in some previous round +// 28: upon from proposer(hP, roundP) AND 2f + 1 +// while stepP = propose ∧ (vr >= 0 ∧ vr < roundP) do +// 29: if valid(v) ∧ (lockedRoundP ≤ vr ∨ lockedValueP = v) then +// 30: broadcast +// 31: else +// 32: broadcast +// 33: stepP ← prevote +// +// NOTE: the proposer for view (height, round) will send ONLY 1 proposal, be it a new one or an old agreed value +func (t *Tendermint) runPropose(ctx context.Context) { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + lockedRound = t.state.lockedRound + lockedValue = t.state.lockedValue + ) + + t.logger.Debug( + "entering propose state", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // Check if the current process is the proposer for this view + if t.verifier.IsProposer(t.node.ID(), height, round) { + // Start the round by constructing and broadcasting a proposal + t.startRound(height, round) + + return + } + + // The current process is NOT the proposer, schedule a timeout + // + // 21: schedule OnTimeoutPropose(hP , roundP) to be executed after timeoutPropose(roundP) + var ( + expiredCh = make(chan struct{}, 1) + timerCtx, cancelTimeoutFn = context.WithCancel(ctx) + timeoutPropose = t.timeouts[propose].CalculateTimeout(round) + ) + + // Defer the timeout timer cancellation + defer cancelTimeoutFn() + + t.logger.Debug( + "scheduling timeoutPropose", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.Duration("timeout", timeoutPropose), + slog.String("node", string(t.node.ID())), + ) + + t.scheduleTimeout(timerCtx, timeoutPropose, expiredCh) + + // Subscribe to all propose messages + // (=current height; unique; >= current round) + ch, unsubscribeFn := t.store.subscribeToPropose() + defer unsubscribeFn() + + // Set up the verification callback. + // The idea is to get the single proposal from the proposer for the view (height, round), + // and verify if it is valid. + // If it turns out the proposal is not valid (the first one received), + // then the protocol needs to move to the prevote state, after + // broadcasting a PREVOTE message with a NIL ID + isFromProposerFn := func(proposal *types.ProposalMessage) bool { + // Make sure the proposal view matches the process view + if round != proposal.GetView().GetRound() { + return false + } + + // Check if the proposal came from the proposer + // for the current view + return t.verifier.IsProposer(proposal.GetSender(), height, round) + } + + // Validates the proposal by examining the proposal params + isValidProposal := func(proposal []byte, proposalRound int64) bool { + // Basic proposal message verification + if proposalRound < 0 { + // Make sure there is no locked round (-1), OR + // that the locked value matches the proposal value + if lockedRound != -1 && !bytes.Equal(lockedValue, proposal) { + return false + } + } else { + // Make sure the proposal round is an earlier round + // than the current process round (old proposal) + if proposalRound >= int64(round) { + return false + } + + // Make sure the locked round value is <= the proposal round, OR + // that the locked value matches the proposal value + if lockedRound > proposalRound && !bytes.Equal(lockedValue, proposal) { + return false + } + } + + // Make sure the proposal itself is valid + return t.verifier.IsValidProposal(proposal, height) + } + + // Create the message cache (local to this context only) + cache := newMessageCache[*types.ProposalMessage](isFromProposerFn) + + for { + select { + case <-ctx.Done(): + return + case <-expiredCh: + // Broadcast a PREVOTE message with a NIL ID + // 59: broadcast ⟨PREVOTE, hP, roundP, nil⟩ + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(nil)) + + // Move to the prevote state + // 60: stepP ← prevote + t.state.step.set(prevote) + + return + case getMessagesFn := <-ch: + // Add the messages to the cache + cache.addMessages(getMessagesFn()) + + // Check if at least 1 proposal message is valid, + // after validation and filtering + proposalMessages := cache.getMessages() + + if len(proposalMessages) == 0 { + // No valid proposal message yet + continue + } + + proposalMessage := proposalMessages[0] + + // Validate the proposal received + if !isValidProposal(proposalMessage.Proposal, proposalMessage.ProposalRound) { + // Broadcast a PREVOTE message with a NIL ID + // 26: broadcast ⟨PREVOTE, hP, roundP, nil⟩ + // 32: broadcast ⟨PREVOTE, hP, roundP, nil⟩ + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(nil)) + + // Move to the prevote state + // 27: stepP ← prevote + // 33: stepP ← prevote + t.state.step.set(prevote) + + t.logger.Debug( + "received invalid proposal", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + return + } + + // Get the proposal from the message + proposal := proposalMessage.GetProposal() + + // Generate the proposal ID + id := t.node.Hash(proposal) + + // Accept the proposal, since it is valid + t.state.acceptedProposal = proposal + t.state.acceptedProposalID = id + + // Broadcast the PREVOTE message with a valid ID + // 24: broadcast ⟨PREVOTE, hP, roundP, id(v)⟩ + // 30: broadcast ⟨PREVOTE, hP, roundP, id(v)⟩ + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(id)) + + // Move to the prevote state + // 27: stepP ← prevote + // 33: stepP ← prevote + t.state.step.set(prevote) + + return + } + } +} + +// runPrevote runs the prevote state in which the process +// waits for a valid PREVOTE messages. +// This state handles the following situations: +// +// - A validator has received 2F+1 PREVOTE messages with a valid ID for the previously accepted proposal +// 36: upon ... AND 2f + 1 while valid(v) ∧ stepP ≥ prevote for the first time do +// 37: if stepP = prevote then +// 38: lockedValueP ← v +// 39: lockedRoundP ← roundP +// 40: broadcast +// 41: stepP ← precommit +// 42: validValueP ← v +// 43: validRoundP ← roundP +// +// - A validator has received 2F+1 PREVOTE messages with a NIL ID +// 44: upon 2f + 1 ⟨PREVOTE, hp, roundP, nil⟩ while stepP = prevote do +// 45: broadcast ⟨PRECOMMIT, hp, roundP, nil⟩ +// 46: stepP ← precommit + +// - A validator has received 2F+1 PREVOTE messages with any kind of ID (valid / NIL) +// 34: upon 2f + 1 while stepP = prevote for the first time do +// 35: schedule OnTimeoutPrevote(hP , roundP) to be executed after timeoutPrevote(roundP) +func (t *Tendermint) runPrevote(ctx context.Context) { + var ( + height = t.state.getHeight() + round = t.state.getRound() + acceptedProposalID = t.state.acceptedProposalID + + expiredCh = make(chan struct{}, 1) + timeoutCtx, cancelTimeoutFn = context.WithCancel(ctx) + timeoutPrevote = t.timeouts[prevote].CalculateTimeout(round) + ) + + t.logger.Debug( + "entering prevote state", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // Defer the timeout timer cancellation + defer cancelTimeoutFn() + + // Subscribe to all prevote messages + // (=current height; unique; >= current round) + ch, unsubscribeFn := t.store.subscribeToPrevote() + defer unsubscribeFn() + + var ( + isValidFn = func(prevote *types.PrevoteMessage) bool { + // Make sure the prevote view matches the process view + return round == prevote.GetView().GetRound() + } + nilMiddleware = func(prevote *types.PrevoteMessage) bool { + // Make sure the ID is NIL + return prevote.Identifier == nil + } + matchingIDMiddleware = func(prevote *types.PrevoteMessage) bool { + // Make sure the ID matches the accepted proposal ID + return bytes.Equal(acceptedProposalID, prevote.Identifier) + } + ) + + var ( + summedPrevotes = newMessageCache[*types.PrevoteMessage](isValidFn) + nilCache = newMessageCache[*types.PrevoteMessage](nilMiddleware) + nonNilCache = newMessageCache[*types.PrevoteMessage](matchingIDMiddleware) + + timeoutScheduled = false + ) + + for { + select { + case <-ctx.Done(): + return + case <-expiredCh: + // Build and broadcast the prevote message, with an ID of NIL + t.broadcast.BroadcastPrecommit(t.buildPrecommitMessage(nil)) + + t.state.step.set(precommit) + + return + case getMessagesFn := <-ch: + // Combine the prevote messages (NIL and non-NIL) + summedPrevotes.addMessages(getMessagesFn()) + prevotes := summedPrevotes.getMessages() + + convertedMessages := make([]Message, 0, len(prevotes)) + messages.ConvertToInterface( + prevotes, + func(m *types.PrevoteMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there is a super majority for the sum prevotes, to schedule a timeout + if !timeoutScheduled && t.hasSuperMajority(convertedMessages) { + // 35: schedule OnTimeoutPrevote(hp, roundP) to be executed after timeoutPrevote(roundP) + t.logger.Debug( + "scheduling timeoutPrevote", + slog.Uint64("round", round), + slog.Duration("timeout", timeoutPrevote), + slog.String("node", string(t.node.ID())), + ) + + t.scheduleTimeout(timeoutCtx, timeoutPrevote, expiredCh) + + timeoutScheduled = true + } + + // Filter the NIL prevote messages + nilCache.addMessages(prevotes) + nilPrevotes := nilCache.getMessages() + + convertedMessages = make([]Message, 0, len(nilPrevotes)) + messages.ConvertToInterface( + nilPrevotes, + func(m *types.PrevoteMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there are 2F+1 NIL prevote messages + if t.hasSuperMajority(convertedMessages) { + // 45: broadcast ⟨PRECOMMIT, hp, roundP, nil⟩ + // 46: stepP ← precommit + t.broadcast.BroadcastPrecommit(t.buildPrecommitMessage(nil)) + t.state.step.set(precommit) + + return + } + + // Filter the non-NIL prevote messages + nonNilCache.addMessages(prevotes) + nonNilPrevotes := nonNilCache.getMessages() + + convertedMessages = make([]Message, 0, len(nonNilPrevotes)) + messages.ConvertToInterface( + nonNilPrevotes, + func(m *types.PrevoteMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there are 2F+1 non-NIL prevote messages + if t.hasSuperMajority(convertedMessages) { + // 38: lockedValueP ← v + // 39: lockedRoundP ← roundP + t.state.lockedRound = int64(round) + t.state.lockedValue = t.state.acceptedProposal + + // 40: broadcast + t.broadcast.BroadcastPrecommit(t.buildPrecommitMessage(acceptedProposalID)) + + // 41: stepP ← precommit + t.state.step.set(precommit) + + // 42: validValueP ← v + // 43: validRoundP ← roundP + t.state.validValue = t.state.acceptedProposal + t.state.validRound = int64(round) + + return + } + } + } +} + +// runPrecommit runs the precommit state in which the process +// waits for a valid PRECOMMIT messages. +// This state handles the following situations: +// +// - A validator has received 2F+1 PRECOMMIT messages with a valid ID for the previously accepted proposal +// 49: upon from proposer(hP, r) AND 2f + 1 +// while decisionP[hP] = nil do +// 50: if valid(v) then +// 51: decisionP[hp] = v +// 52: hP ← hP + 1 +// 53: reset lockedRoundP, lockedValueP, validRoundP and validValueP to initial values and empty message log +// 54: StartRound(0) +// +// - A validator has received 2F+1 PRECOMMIT messages with any value (valid ID or NIL) +// 47: upon 2f + 1 for the first time do +// 48: schedule OnTimeoutPrecommit(hP , roundP) to be executed after timeoutPrecommit(roundP) +func (t *Tendermint) runPrecommit(ctx context.Context) *FinalizedProposal { + var ( + height = t.state.getHeight() + round = t.state.getRound() + acceptedProposalID = t.state.acceptedProposalID + + expiredCh = make(chan struct{}, 1) + timeoutCtx, cancelTimeoutFn = context.WithCancel(ctx) + timeoutPrecommit = t.timeouts[precommit].CalculateTimeout(round) + ) + + t.logger.Debug( + "entering precommit state", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // Defer the timeout timer cancellation + defer cancelTimeoutFn() + + // Subscribe to all precommit messages + // (=current height; unique; >= current round) + ch, unsubscribeFn := t.store.subscribeToPrecommit() + defer unsubscribeFn() + + var ( + isValidFn = func(precommit *types.PrecommitMessage) bool { + // Make sure the precommit view matches the process view + return round == precommit.GetView().GetRound() + } + nonNilIDFn = func(precommit *types.PrecommitMessage) bool { + // Make sure the precommit ID is not nil + if precommit.Identifier == nil { + return false + } + + // Make sure the ID matches the accepted proposal ID + return bytes.Equal(acceptedProposalID, precommit.Identifier) + } + ) + + var ( + summedPrecommits = newMessageCache[*types.PrecommitMessage](isValidFn) + nonNilCache = newMessageCache[*types.PrecommitMessage](nonNilIDFn) + + timeoutScheduled = false + ) + + for { + select { + case <-ctx.Done(): + // Context cancelled, no proposal is finalized + return nil + case <-expiredCh: + // Timeout triggered, no proposal is finalized + return nil + case getMessagesFn := <-ch: + // Combine the precommit messages (NIL and non-NIL) + summedPrecommits.addMessages(getMessagesFn()) + precommits := summedPrecommits.getMessages() + + convertedMessages := make([]Message, 0, len(precommits)) + messages.ConvertToInterface( + precommits, + func(m *types.PrecommitMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there is a super majority for the sum precommits, to schedule a timeout + if !timeoutScheduled && t.hasSuperMajority(convertedMessages) { + // 48: schedule OnTimeoutPrecommit(hP, roundP) to be executed after timeoutPrecommit(roundP) + t.logger.Debug( + "scheduling timeoutPrecommit", + slog.Uint64("round", round), + slog.Duration("timeout", timeoutPrecommit), + slog.String("node", string(t.node.ID())), + ) + + t.scheduleTimeout(timeoutCtx, timeoutPrecommit, expiredCh) + + timeoutScheduled = true + } + + // Filter the non-NIL precommit messages + nonNilCache.addMessages(precommits) + nonNilPrecommits := nonNilCache.getMessages() + + convertedMessages = make([]Message, 0, len(nonNilPrecommits)) + messages.ConvertToInterface( + nonNilPrecommits, + func(m *types.PrecommitMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there are 2F+1 non-NIL precommit messages + if t.hasSuperMajority(convertedMessages) { + return newFinalizedProposal( + t.state.acceptedProposal, + t.state.acceptedProposalID, + ) + } + } + } +} diff --git a/tm2/pkg/libtm/core/tendermint_test.go b/tm2/pkg/libtm/core/tendermint_test.go new file mode 100644 index 00000000000..db80ba04956 --- /dev/null +++ b/tm2/pkg/libtm/core/tendermint_test.go @@ -0,0 +1,2258 @@ +package core + +import ( + "bytes" + "context" + "fmt" + "testing" + "time" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTendermint_AddMessage_Invalid(t *testing.T) { + t.Parallel() + + t.Run("invalid signature", func(t *testing.T) { + t.Parallel() + + var ( + signature = []byte("invalid signature") + message = &types.PrevoteMessage{ + View: &types.View{}, + Sender: []byte{}, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return false + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(_ []byte) bool { + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrInvalidMessageSignature, + ) + }) + + t.Run("sender is not a validator", func(t *testing.T) { + t.Parallel() + + var ( + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: &types.View{}, + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return false + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrMessageFromNonValidator, + ) + }) + + t.Run("message is for an earlier height", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 10, + Round: 0, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: &types.View{ + Height: currentView.Height - 1, // earlier height + Round: currentView.Round, + }, + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrEarlierHeightMessage, + ) + }) + + t.Run("message is for an earlier round", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: &types.View{ + Height: currentView.Height, + Round: currentView.Round - 1, // earlier round + }, + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrEarlierRoundMessage, + ) + }) + + t.Run("invalid proposal message payload", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.ProposalMessage{ + View: nil, // invalid view + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddProposalMessage(message), + types.ErrInvalidMessageView, + ) + }) + + t.Run("invalid prevote message payload", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: nil, // invalid view + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + types.ErrInvalidMessageView, + ) + }) + + t.Run("invalid precommit message payload", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrecommitMessage{ + View: nil, // invalid view + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrecommitMessage(message), + types.ErrInvalidMessageView, + ) + }) +} + +func TestTendermint_AddMessage_Valid(t *testing.T) { + t.Parallel() + + t.Run("valid proposal message", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + + signature = []byte("valid signature") + sender = []byte("sender") + proposal = []byte("proposal") + + message = &types.ProposalMessage{ + View: currentView, + Sender: sender, + Proposal: proposal, + ProposalRound: -1, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + sub, unsubFn := tm.store.subscribeToPropose() + defer unsubFn() + + // Make sure the message is added + require.NoError(t, tm.AddProposalMessage(message)) + + // Make sure the message is present in the store + var messages []*types.ProposalMessage + select { + case getMessages := <-sub: + messages = getMessages() + case <-time.After(5 * time.Second): + } + + require.Len(t, messages, 1) + + storeMessage := messages[0] + + assert.Equal( + t, + message.GetProposal(), + storeMessage.GetProposal(), + ) + assert.Equal( + t, + message.GetProposalRound(), + storeMessage.GetProposalRound(), + ) + assert.Equal( + t, + message.GetView(), + storeMessage.GetView(), + ) + assert.Equal( + t, + message.GetSender(), + storeMessage.GetSender(), + ) + }) + + t.Run("valid prevote message", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + + signature = []byte("valid signature") + sender = []byte("sender") + id = []byte("prevote ID") + + message = &types.PrevoteMessage{ + View: currentView, + Sender: sender, + Identifier: id, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + sub, unsubFn := tm.store.subscribeToPrevote() + defer unsubFn() + + // Make sure the message is added + require.NoError(t, tm.AddPrevoteMessage(message)) + + // Make sure the message is present in the store + var messages []*types.PrevoteMessage + select { + case getMessages := <-sub: + messages = getMessages() + case <-time.After(5 * time.Second): + } + + require.Len(t, messages, 1) + + storeMessage := messages[0] + + assert.Equal( + t, + message.GetIdentifier(), + storeMessage.GetIdentifier(), + ) + assert.Equal( + t, + message.GetView(), + storeMessage.GetView(), + ) + assert.Equal( + t, + message.GetSender(), + storeMessage.GetSender(), + ) + }) + + t.Run("valid precommit message", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + + signature = []byte("valid signature") + sender = []byte("sender") + id = []byte("precommit ID") + + message = &types.PrecommitMessage{ + View: currentView, + Sender: sender, + Identifier: id, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + sub, unsubFn := tm.store.subscribeToPrecommit() + defer unsubFn() + + // Make sure the message is added + require.NoError(t, tm.AddPrecommitMessage(message)) + + // Make sure the message is present in the store + var messages []*types.PrecommitMessage + select { + case getMessages := <-sub: + messages = getMessages() + case <-time.After(5 * time.Second): + } + + require.Len(t, messages, 1) + + storeMessage := messages[0] + + assert.Equal( + t, + message.GetIdentifier(), + storeMessage.GetIdentifier(), + ) + assert.Equal( + t, + message.GetView(), + storeMessage.GetView(), + ) + assert.Equal( + t, + message.GetSender(), + storeMessage.GetSender(), + ) + }) +} + +func TestTendermint_FinalizeProposal_Propose(t *testing.T) { + t.Parallel() + + t.Run("validator is the proposer", func(t *testing.T) { + t.Parallel() + + t.Run("validator builds new proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposal = []byte("proposal") + + broadcastPropose *types.ProposalMessage + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + require.Equal(t, id, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + buildProposalFn: func(h uint64) []byte { + require.Equal(t, view.GetHeight(), h) + + return proposal + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + } + mockBroadcast = &mockBroadcast{ + broadcastProposeFn: func(proposalMessage *types.ProposalMessage) { + broadcastPropose = proposalMessage + }, + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast propose was valid + require.NotNil(t, broadcastPropose) + require.NotNil(t, tm.state.acceptedProposal) + assert.Equal(t, broadcastPropose.GetProposal(), tm.state.acceptedProposal) + + assert.True(t, view.Equals(broadcastPropose.GetView())) + assert.Equal(t, id, broadcastPropose.GetSender()) + assert.Equal(t, signature, broadcastPropose.GetSignature()) + assert.Equal(t, proposal, broadcastPropose.GetProposal()) + assert.EqualValues(t, -1, broadcastPropose.GetProposalRound()) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator uses old proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 10, + } + proposal = []byte("old proposal") + proposalRound = int64(5) + + broadcastPropose *types.ProposalMessage + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + require.Equal(t, id, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + buildProposalFn: func(_ uint64) []byte { + t.FailNow() + + return nil + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + } + mockBroadcast = &mockBroadcast{ + broadcastProposeFn: func(proposalMessage *types.ProposalMessage) { + broadcastPropose = proposalMessage + }, + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the old proposal + tm.state.validValue = proposal + tm.state.validRound = proposalRound + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast propose was valid + require.NotNil(t, broadcastPropose) + require.NotNil(t, tm.state.acceptedProposal) + assert.Equal(t, broadcastPropose.GetProposal(), tm.state.acceptedProposal) + + assert.True(t, view.Equals(broadcastPropose.GetView())) + assert.Equal(t, id, broadcastPropose.GetSender()) + assert.Equal(t, signature, broadcastPropose.GetSignature()) + assert.Equal(t, proposal, broadcastPropose.GetProposal()) + assert.EqualValues(t, proposalRound, broadcastPropose.GetProposalRound()) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + }) + + t.Run("validator is not the proposer", func(t *testing.T) { + t.Parallel() + + t.Run("validator receives valid fresh proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: -1, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the correct proposal was accepted + assert.Equal(t, proposalMessage.GetProposal(), tm.state.acceptedProposal) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives valid locked proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 10, + } + proposal = []byte("proposal") + proposalRound = int64(5) + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the correct proposal was accepted + assert.Equal(t, proposalMessage.GetProposal(), tm.state.acceptedProposal) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives invalid fresh proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 5, + } + proposal = []byte("proposal") + + lockedRound = int64(view.Round - 1) // earlier round + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: -1, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the locked round + tm.state.lockedRound = lockedRound + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives locked proposal from an invalid round", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 5, + } + proposal = []byte("proposal") + + lockedRound = int64(view.Round - 1) // earlier round + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: lockedRound + 1, // invalid proposal round + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the locked round + tm.state.lockedRound = lockedRound + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives invalid locked proposal (round mismatch)", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 5, + } + proposal = []byte("proposal") + + lockedRound = int64(view.Round - 1) // earlier round + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: lockedRound - 1, // invalid proposal round + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the locked round + tm.state.lockedRound = lockedRound + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives a proposal that is not valid", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: -1, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return false // invalid proposal + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator does not receive a proposal in time", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + + timeout = Timeout{ + Initial: 100 * time.Millisecond, + Delta: 0, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + WithProposeTimeout(timeout), + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + }) +} + +// generatePrevoteMessages generates basic prevote messages +// using the given view and ID +func generatePrevoteMessages( + t *testing.T, + count int, + view *types.View, + id []byte, +) []*types.PrevoteMessage { + t.Helper() + + messages := make([]*types.PrevoteMessage, count) + + for i := 0; i < count; i++ { + messages[i] = &types.PrevoteMessage{ + View: view, + Sender: []byte(fmt.Sprintf("sender %d", i)), + Signature: []byte("signature"), + Identifier: id, + } + } + + return messages +} + +// generatePrecommitMessages generates basic precommit messages +// using the given view and ID +func generatePrecommitMessages( + t *testing.T, + count int, + view *types.View, + id []byte, +) []*types.PrecommitMessage { + t.Helper() + + messages := make([]*types.PrecommitMessage, count) + + for i := 0; i < count; i++ { + messages[i] = &types.PrecommitMessage{ + View: view, + Sender: []byte(fmt.Sprintf("sender %d", i)), + Signature: []byte("signature"), + Identifier: id, + } + } + + return messages +} + +func TestTendermint_FinalizeProposal_Prevote(t *testing.T) { + t.Parallel() + + t.Run("validator received 2F+1 PREVOTEs with a valid ID", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: []byte("proposer"), + Signature: []byte("proposer signature"), + Proposal: proposal, + ProposalRound: -1, + } + + numPrevotes = 10 + prevoteMessages = generatePrevoteMessages(t, numPrevotes, view, proposalID) + + broadcastPrecommit *types.PrecommitMessage + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(numPrevotes) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrecommitFn: func(precommitMessage *types.PrecommitMessage) { + broadcastPrecommit = precommitMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = prevote + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in 2F+1 non-NIL prevote messages + for _, prevoteMessage := range prevoteMessages { + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, precommit, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.EqualValues(t, view.Round, tm.state.lockedRound) + assert.Equal(t, proposalMessage.GetProposal(), tm.state.lockedValue) + assert.Equal(t, proposalMessage.GetProposal(), tm.state.validValue) + assert.EqualValues(t, view.Round, tm.state.validRound) + + // Make sure the broadcast precommit was valid + require.NotNil(t, broadcastPrecommit) + assert.True(t, view.Equals(broadcastPrecommit.GetView())) + assert.Equal(t, id, broadcastPrecommit.GetSender()) + assert.Equal(t, signature, broadcastPrecommit.GetSignature()) + assert.Equal(t, proposalID, broadcastPrecommit.GetIdentifier()) + }) + + t.Run("validator received 2F+1 PREVOTEs with a NIL ID", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + numPrevotes = 10 + prevoteMessages = generatePrevoteMessages(t, numPrevotes, view, nil) + + broadcastPrecommit *types.PrecommitMessage + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(numPrevotes) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrecommitFn: func(precommitMessage *types.PrecommitMessage) { + broadcastPrecommit = precommitMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = prevote + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in 2F+1 non-NIL prevote messages + for _, prevoteMessage := range prevoteMessages { + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, precommit, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + assert.EqualValues(t, -1, tm.state.lockedRound) + assert.Nil(t, tm.state.lockedValue) + assert.Nil(t, tm.state.validValue) + assert.EqualValues(t, -1, tm.state.validRound) + + // Make sure the broadcast precommit was valid + require.NotNil(t, broadcastPrecommit) + assert.True(t, view.Equals(broadcastPrecommit.GetView())) + assert.Equal(t, id, broadcastPrecommit.GetSender()) + assert.Equal(t, signature, broadcastPrecommit.GetSignature()) + assert.Nil(t, broadcastPrecommit.GetIdentifier()) + }) + + t.Run("validator does not receive quorum PREVOTEs in time", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + totalPrevoteCount = 10 + nilPrevoteMessages = generatePrevoteMessages(t, totalPrevoteCount/2, view, nil) + nonNilPrevoteMessages = generatePrevoteMessages(t, totalPrevoteCount/2, view, proposalID) + + timeout = Timeout{ + Initial: 100 * time.Millisecond, + Delta: 0, + } + + broadcastPrecommit *types.PrecommitMessage + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(totalPrevoteCount) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrecommitFn: func(precommitMessage *types.PrecommitMessage) { + broadcastPrecommit = precommitMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + WithPrevoteTimeout(timeout), + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = prevote + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in non-NIL prevote messages + for index, prevoteMessage := range nonNilPrevoteMessages { + // Change the senders for the non-NIL prevote messages. + // The reason the senders need to be changed is, so we can simulate the following scenario: + // 1/2 of the total voting power sent in non-NIL prevote messages + // 1/2 of the total voting power sent in NIL prevote messages + // In turn, there is a super majority when their voting powers are summed (non-NIL and NIL) + prevoteMessage.Sender = []byte(fmt.Sprintf("sender %d", index)) + + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Add in NIL prevote messages + for index, prevoteMessage := range nilPrevoteMessages { + // Change the senders for the NIL prevote messages + prevoteMessage.Sender = []byte(fmt.Sprintf("sender %d", index+len(nonNilPrevoteMessages))) + + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, precommit, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + assert.EqualValues(t, -1, tm.state.lockedRound) + assert.Nil(t, tm.state.lockedValue) + assert.Nil(t, tm.state.validValue) + assert.EqualValues(t, -1, tm.state.validRound) + + // Make sure the broadcast precommit was valid + require.NotNil(t, broadcastPrecommit) + assert.True(t, view.Equals(broadcastPrecommit.GetView())) + assert.Equal(t, id, broadcastPrecommit.GetSender()) + assert.Equal(t, signature, broadcastPrecommit.GetSignature()) + assert.Nil(t, broadcastPrecommit.GetIdentifier()) + }) +} + +func TestTendermint_FinalizeProposal_Precommit(t *testing.T) { + t.Parallel() + + t.Run("validator received 2F+1 PRECOMMITs with a valid ID", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + Proposal: proposal, + } + + numPrecommits = 10 + precommitMessages = generatePrecommitMessages(t, numPrecommits, view, proposalID) + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(numPrecommits) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, &mockBroadcast{}, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = precommit + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in 2F+1 non-NIL precommit messages + for _, precommitMessage := range precommitMessages { + require.NoError(t, tm.AddPrecommitMessage(precommitMessage)) + } + + // Run through the states + finalizedProposalCh := tm.finalizeProposal(ctx) + + // Get the finalized proposal + finalizedProposal := <-finalizedProposalCh + + cancelFn() + tm.wg.Wait() + + // Make sure the finalized proposal is valid + require.NotNil(t, finalizedProposal) + assert.Equal(t, proposalMessage.Proposal, finalizedProposal.Data) + }) + + t.Run("validator does not receive quorum PRECOMMITs in time", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + totalPrecommitCount = 10 + nilPrecommitMessages = generatePrecommitMessages(t, totalPrecommitCount/2, view, nil) + nonNilPrecommitMessages = generatePrecommitMessages(t, totalPrecommitCount/2, view, proposalID) + + timeout = Timeout{ + Initial: 100 * time.Millisecond, + Delta: 0, + } + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(totalPrecommitCount) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + &mockBroadcast{}, + mockSigner, + WithPrecommitTimeout(timeout), + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = precommit + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in non-NIL precommit messages + for index, precommitMessage := range nonNilPrecommitMessages { + // Change the senders for the non-NIL precommit messages. + // The reason the senders need to be changed is, so we can simulate the following scenario: + // 1/2 of the total voting power sent in non-NIL precommit messages + // 1/2 of the total voting power sent in NIL precommit messages + // In turn, there is a super majority when their voting powers are summed (non-NIL and NIL) + precommitMessage.Sender = []byte(fmt.Sprintf("sender %d", index)) + + require.NoError(t, tm.AddPrecommitMessage(precommitMessage)) + } + + // Add in NIL precommit messages + for index, precommitMessage := range nilPrecommitMessages { + // Change the senders for the NIL precommit messages + precommitMessage.Sender = []byte(fmt.Sprintf("sender %d", index+len(nonNilPrecommitMessages))) + + require.NoError(t, tm.AddPrecommitMessage(precommitMessage)) + } + + // Run through the states + finalizedProposal := <-tm.finalizeProposal(ctx) + + cancelFn() + tm.wg.Wait() + + // Make sure the finalized proposal is NIL + assert.Nil(t, finalizedProposal) + }) +} + +func TestTendermint_WatchForFutureRounds(t *testing.T) { + t.Parallel() + + var ( + processView = &types.View{ + Height: 10, + Round: 5, + } + view = &types.View{ + Height: processView.Height, + Round: processView.Round + 5, // higher round + } + + totalMessagesPerType = 10 + prevoteMessages = generatePrevoteMessages(t, totalMessagesPerType/4, view, []byte("proposal ID")) + precommitMessages = generatePrecommitMessages(t, totalMessagesPerType, view, []byte("proposal ID")) + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(len(prevoteMessages) + len(precommitMessages)) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return []byte("signature") + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + nil, + nil, + mockSigner, + ) + + // Set the process view + tm.state.setHeight(processView.GetHeight()) + tm.state.setRound(processView.GetRound()) + + // Make sure F+1 messages are added to the + // message queue, with a higher round + for _, message := range prevoteMessages { + require.NoError(t, tm.AddPrevoteMessage(message)) + } + + for _, message := range precommitMessages { + require.NoError(t, tm.AddPrecommitMessage(message)) + } + + // Set up the wait context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + // Wait for future round jumps + nextRound := <-tm.watchForRoundJumps(ctx) + + tm.wg.Wait() + + // Make sure the correct round was returned + assert.Equal(t, view.GetRound(), nextRound) +} diff --git a/tm2/pkg/libtm/core/timeout.go b/tm2/pkg/libtm/core/timeout.go new file mode 100644 index 00000000000..71462f87a3a --- /dev/null +++ b/tm2/pkg/libtm/core/timeout.go @@ -0,0 +1,62 @@ +package core + +import ( + "context" + "time" +) + +// getDefaultTimeoutMap returns the default timeout map +// for the Tendermint consensus engine +func getDefaultTimeoutMap() map[step]Timeout { + return map[step]Timeout{ + propose: { + Initial: 10 * time.Second, // 10s + Delta: 500 * time.Millisecond, // 0.5 + }, + prevote: { + Initial: 10 * time.Second, // 10s + Delta: 500 * time.Millisecond, // 0.5 + }, + precommit: { + Initial: 10 * time.Second, // 10s + Delta: 500 * time.Millisecond, // 0.5 + }, + } +} + +// Timeout is a holder for timeout duration information (constant) +type Timeout struct { + Initial time.Duration // the initial timeout duration + Delta time.Duration // the delta for future timeouts +} + +// CalculateTimeout calculates a new timeout duration using +// the formula: +// +// timeout(r) = initTimeout + r * timeoutDelta +func (t Timeout) CalculateTimeout(round uint64) time.Duration { + return t.Initial + time.Duration(round)*t.Delta +} + +// scheduleTimeout schedules a state timeout to be executed +func (t *Tendermint) scheduleTimeout( + ctx context.Context, + timeout time.Duration, + expiredCh chan<- struct{}, +) { + t.wg.Add(1) + + go func() { + defer t.wg.Done() + + select { + case <-ctx.Done(): + case <-time.After(timeout): + // Signal that the state expired + select { + case expiredCh <- struct{}{}: + default: + } + } + }() +} diff --git a/tm2/pkg/libtm/core/timeout_test.go b/tm2/pkg/libtm/core/timeout_test.go new file mode 100644 index 00000000000..ec57cc385fd --- /dev/null +++ b/tm2/pkg/libtm/core/timeout_test.go @@ -0,0 +1,93 @@ +package core + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimeout_CalculateTimeout(t *testing.T) { + t.Parallel() + + var ( + initial = 10 * time.Second + delta = 200 * time.Millisecond + + tm = Timeout{ + Initial: initial, + Delta: delta, + } + ) + + for round := uint64(0); round < 100; round++ { + assert.Equal( + t, + initial+time.Duration(round)*delta, + tm.CalculateTimeout(round), + ) + } +} + +func TestTimeout_ScheduleTimeoutPropose(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + step step + }{ + { + "OnTimeoutPropose", + propose, + }, + { + "OnTimeoutPrevote", + prevote, + }, + { + "OnTimeoutPrecommit", + precommit, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + expiredCh := make(chan struct{}, 1) + + tm := NewTendermint( + nil, + nil, + nil, + nil, + ) + + // Set the timeout data for the step + tm.timeouts[testCase.step] = Timeout{ + Initial: 50 * time.Millisecond, + Delta: 50 * time.Millisecond, + } + + // Schedule the timeout + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + timeoutPropose := tm.timeouts[testCase.step].CalculateTimeout(0) + + tm.scheduleTimeout(ctx, timeoutPropose, expiredCh) + + // Wait for the timer to trigger + select { + case <-time.After(5 * time.Second): + t.Fatal("timer not triggered") + case <-expiredCh: + } + + tm.wg.Wait() + }) + } +} diff --git a/tm2/pkg/libtm/core/types.go b/tm2/pkg/libtm/core/types.go new file mode 100644 index 00000000000..ba31f454d90 --- /dev/null +++ b/tm2/pkg/libtm/core/types.go @@ -0,0 +1,83 @@ +package core + +import ( + "github.com/gnolang/libtm/messages/types" +) + +// Signer is an abstraction over the signature manipulation process +type Signer interface { + // Sign generates a signature for the given raw data + Sign(data []byte) []byte + + // IsValidSignature verifies whether the signature matches the raw data + IsValidSignature(data []byte, signature []byte) bool +} + +// Verifier is an abstraction over the outer consensus calling context +// that has access to validator set information +type Verifier interface { + // IsProposer checks if the given ID matches the proposer for the given height + IsProposer(id []byte, height uint64, round uint64) bool + + // IsValidator checks if the given message sender ID belongs to a validator + IsValidator(id []byte) bool + + // IsValidProposal checks if the given proposal is valid, for the given height + IsValidProposal(proposal []byte, height uint64) bool + + // GetSumVotingPower returns the summed voting power from + // the given unique message authors + GetSumVotingPower(msgs []Message) uint64 + + // GetTotalVotingPower returns the total voting power + // of the entire validator set for the given height + GetTotalVotingPower(height uint64) uint64 +} + +// Node interface is an abstraction over a single entity (current process) that runs +// the consensus algorithm +type Node interface { + // ID returns the ID associated with the current process (validator) + ID() []byte + + // Hash generates a hash of the given data. + // It must not modify the slice proposal, even temporarily + // and must not retain the data + Hash(proposal []byte) []byte + + // BuildProposal generates a raw proposal for the given height + BuildProposal(height uint64) []byte +} + +// Broadcast is an abstraction over the networking / message sharing interface +// that enables message passing between validators +type Broadcast interface { + // BroadcastPropose broadcasts a PROPOSAL message + BroadcastPropose(message *types.ProposalMessage) + + // BroadcastPrevote broadcasts a PREVOTE message + BroadcastPrevote(message *types.PrevoteMessage) + + // BroadcastPrecommit broadcasts a PRECOMMIT message + BroadcastPrecommit(message *types.PrecommitMessage) +} + +// Message is the content being passed around +// between consensus validators. +// Message types: PROPOSAL, PREVOTE, PRECOMMIT +type Message interface { + // GetView fetches the message view + GetView() *types.View + + // GetSender fetches the message sender + GetSender() []byte + + // GetSignature fetches the message signature + GetSignature() []byte + + // GetSignaturePayload fetches the signature payload (sign data) + GetSignaturePayload() []byte + + // Verify verifies the message content is valid (base verification) + Verify() error +} diff --git a/tm2/pkg/libtm/go.mod b/tm2/pkg/libtm/go.mod new file mode 100644 index 00000000000..e903dd664e6 --- /dev/null +++ b/tm2/pkg/libtm/go.mod @@ -0,0 +1,17 @@ +module github.com/gnolang/libtm + +go 1.22 + +toolchain go1.22.4 + +require ( + github.com/rs/xid v1.5.0 + github.com/stretchr/testify v1.9.0 + google.golang.org/protobuf v1.34.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/tm2/pkg/libtm/go.sum b/tm2/pkg/libtm/go.sum new file mode 100644 index 00000000000..2a141a0fa78 --- /dev/null +++ b/tm2/pkg/libtm/go.sum @@ -0,0 +1,18 @@ +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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tm2/pkg/libtm/golangci.yaml b/tm2/pkg/libtm/golangci.yaml new file mode 100644 index 00000000000..00982e70b54 --- /dev/null +++ b/tm2/pkg/libtm/golangci.yaml @@ -0,0 +1,116 @@ +run: + concurrency: 8 + timeout: 10m + issue-exit-code: 1 + tests: true + skip-dirs-use-default: true + modules-download-mode: readonly + allow-parallel-runners: false + go: "" + +output: + uniq-by-line: false + path-prefix: "" + sort-results: true + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + new: false + fix: false + exclude-rules: + - path: (.+)_test.go + linters: + - nilnil + - gosec + +linters: + fast: false + disable-all: true + enable: + - asasalint # Check for pass []any as any in variadic func(...any) + - asciicheck # Detects funky ASCII characters + - bidichk # Checks for dangerous unicode character sequences + - durationcheck # Check for two durations multiplied together + - errcheck # Forces to not skip error check + - exportloopref # Checks for pointers to enclosing loop variables + - gocritic # Bundles different linting checks + - godot # Checks for periods at the end of comments + - gomoddirectives # Allow or ban replace directives in go.mod + - gosimple # Code simplification + - govet # Official Go tool + - ineffassign # Detects when assignments to existing variables are not used + - nakedret # Finds naked/bare returns and requires change them + - nilerr # Requires explicit returns + - nilnil # Requires explicit returns + - promlinter # Lints Prometheus metrics names + - reassign # Checks that package variables are not reassigned + - revive # Drop-in replacement for golint + - tenv # Detects using os.Setenv instead of t.Setenv + - testableexamples # Checks if examples are testable (have expected output) + - unparam # Finds unused params + - usestdlibvars # Detects the possibility to use variables/constants from stdlib + - wastedassign # Finds wasted assignment statements + - loggercheck # Checks the odd number of key and value pairs for common logger libraries + - nestif # Finds deeply nested if statements + - nonamedreturns # Reports all named returns + - decorder # Check declaration order of types, consts, vars and funcs + - gocheckcompilerdirectives # Checks that compiler directive comments (//go:) are valid + - gochecknoinits # Checks for init methods + - whitespace # Tool for detection of leading and trailing whitespace + - wsl # Forces you to use empty lines + - unconvert # Unnecessary type conversions + - tparallel # Detects inappropriate usage of t.Parallel() method in your Go test codes + - thelper # Detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - stylecheck # Stylecheck is a replacement for golint + - prealloc # Finds slice declarations that could potentially be pre-allocated + - predeclared # Finds code that shadows one of Go's predeclared identifiers + - nolintlint # Ill-formed or insufficient nolint directives + - nlreturn # Checks for a new line before return and branch statements to increase code clarity + - misspell # Misspelled English words in comments + - makezero # Finds slice declarations with non-zero initial length + - lll # Long lines + - importas # Enforces consistent import aliases + - gosec # Security problems + - gofmt # Whether the code was gofmt-ed + - gofumpt # Stricter gofmt + - goimports # Unused imports + - goconst # Repeated strings that could be replaced by a constant + - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) + - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - unused # Checks Go code for unused constants, variables, functions and types + +linters-settings: + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - hugeParam + - rangeExprCopy + - rangeValCopy + - importShadow + - unnamedResult + errcheck: + check-type-assertions: false + check-blank: true + exclude-functions: + - io/ioutil.ReadFile + - io.Copy(*bytes.Buffer) + - io.Copy(os.Stdout) + nakedret: + max-func-lines: 1 + govet: + enable-all: true + gofmt: + simplify: true + goconst: + min-len: 3 + min-occurrences: 3 + godot: + scope: all + period: false diff --git a/tm2/pkg/libtm/messages/collector.go b/tm2/pkg/libtm/messages/collector.go new file mode 100644 index 00000000000..e94283f0ac1 --- /dev/null +++ b/tm2/pkg/libtm/messages/collector.go @@ -0,0 +1,176 @@ +package messages + +import ( + "fmt" + "strconv" + "strings" + "sync" + + "github.com/gnolang/libtm/messages/types" +) + +// msgType is the combined message type interface, +// for easy reference and type safety +type msgType interface { + types.ProposalMessage | types.PrevoteMessage | types.PrecommitMessage +} + +// this is because Go doesn't support covariance on slices +// []*T -> []I does not work +func ConvertToInterface[T msgType](msgs []*T, convertFunc func(m *T)) { + for _, msg := range msgs { + convertFunc(msg) + } +} + +type ( + // collection are the actual received messages. + // Maps a unique identifier -> their message (of a specific type) to avoid duplicates. + // Identifiers are derived from . + // Each validator in the consensus needs to send at most 1 message of every type + // (minus the PROPOSAL, which is only sent by the proposer), + // so the message system needs to keep track of only 1 message per type, per validator, per view + collection[T msgType] map[string]*T +) + +// Collector is a single message type collector +type Collector[T msgType] struct { + collection collection[T] // the message storage + subscriptions subscriptions[T] // the active message subscriptions + + collectionMux sync.RWMutex + subscriptionsMux sync.RWMutex +} + +// NewCollector creates a new message collector +func NewCollector[T msgType]() *Collector[T] { + return &Collector[T]{ + collection: make(collection[T]), + subscriptions: make(subscriptions[T]), + } +} + +// Subscribe creates a new collector subscription. +// Returns the channel for receiving messages, +// as well as the unsubscribe method +func (c *Collector[T]) Subscribe() (<-chan func() []*T, func()) { + c.subscriptionsMux.Lock() + defer c.subscriptionsMux.Unlock() + + // Create a new subscription + id, ch := c.subscriptions.add() + + // Create the unsubscribe callback + unsubscribeFn := func() { + c.subscriptionsMux.Lock() + defer c.subscriptionsMux.Unlock() + + c.subscriptions.remove(id) + } + + // Notify the subscription immediately, + // since there can be existing messages in the collection. + // This action assumes the channel is not blocking (created with initial size), + // since the calling context does not have access to it yet at this point + notifySubscription(ch, c.GetMessages) + + return ch, unsubscribeFn +} + +// GetMessages returns the currently present messages in the collector +func (c *Collector[T]) GetMessages() []*T { + c.collectionMux.RLock() + defer c.collectionMux.RUnlock() + + // Fetch the messages in the collection + return c.collection.getMessages() +} + +// getMessages fetches the messages in the collection +func (c *collection[T]) getMessages() []*T { + messages := make([]*T, 0, len(*c)) + + for _, senderMessage := range *c { + messages = append(messages, senderMessage) + } + + return messages +} + +// DropMessages drops all messages from the collection if their view +// is less than the given view (earlier) +func (c *Collector[T]) DropMessages(view *types.View) { + c.collectionMux.RLock() + defer c.collectionMux.RUnlock() + + // Filter out messages from the collection + shouldStay := func(key string) bool { + // Construct the view from the message + messageView := getViewFromKey(key) + + // Only messages who are >= the current view stay + return messageView.Height >= view.Height && + messageView.Round >= view.Round + } + + // Filter out the messages + c.collection.dropMessages(shouldStay) +} + +// dropMessages drops messages from the collection using the given filter +func (c *collection[T]) dropMessages(shouldStay func(string) bool) { + for key := range *c { + if shouldStay(key) { + continue + } + + delete(*c, key) + } +} + +// AddMessage adds a new message to the collector +func (c *Collector[T]) AddMessage(view *types.View, from []byte, message *T) { + c.collectionMux.Lock() + + // Add the message + c.collection.addMessage( + getCollectionKey(from, view), + message, + ) + + c.collectionMux.Unlock() + + // Notify the subscriptions + c.subscriptionsMux.RLock() + defer c.subscriptionsMux.RUnlock() + + c.subscriptions.notify(c.GetMessages) +} + +// addMessage adds a new message to the collection +func (c *collection[T]) addMessage(key string, message *T) { + (*c)[key] = message +} + +// getCollectionKey constructs a key based on the +// message sender and view information. +// This key guarantees uniqueness in the message store +func getCollectionKey(from []byte, view *types.View) string { + return fmt.Sprintf("%s_%d_%d", from, view.Height, view.Round) +} + +// getViewFromKey constructs the view information, +// based on the collection key +func getViewFromKey(key string) *types.View { + // Split the key + keyParts := strings.Split(key, "_") + + // Parse the view values + height, _ := strconv.ParseUint(keyParts[1], 10, 64) //nolint:errcheck // Key is valid + round, _ := strconv.ParseUint(keyParts[2], 10, 64) //nolint:errcheck // Key is valid + + return &types.View{ + Height: height, + Round: round, + } +} diff --git a/tm2/pkg/libtm/messages/collector_test.go b/tm2/pkg/libtm/messages/collector_test.go new file mode 100644 index 00000000000..192b3d088e8 --- /dev/null +++ b/tm2/pkg/libtm/messages/collector_test.go @@ -0,0 +1,440 @@ +package messages + +import ( + "sort" + "strconv" + "sync" + "testing" + "time" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateProposalMessages generates dummy proposal messages +// for the given view and type +func generateProposalMessages( + t *testing.T, + count int, + view *types.View, +) []*types.ProposalMessage { + t.Helper() + + messages := make([]*types.ProposalMessage, 0, count) + + for index := 0; index < count; index++ { + message := &types.ProposalMessage{ + Sender: []byte(strconv.Itoa(index)), + View: view, + } + + messages = append(messages, message) + } + + return messages +} + +// generatePrevoteMessages generates dummy prevote messages +// for the given view and type +func generatePrevoteMessages( + t *testing.T, + count int, + view *types.View, +) []*types.PrevoteMessage { + t.Helper() + + messages := make([]*types.PrevoteMessage, 0, count) + + for index := 0; index < count; index++ { + message := &types.PrevoteMessage{ + Sender: []byte(strconv.Itoa(index)), + View: view, + } + + messages = append(messages, message) + } + + return messages +} + +// generatePrevoteMessages generates dummy prevote messages +// for the given view and type +func generatePrecommitMessages( + t *testing.T, + count int, + view *types.View, +) []*types.PrecommitMessage { + t.Helper() + + messages := make([]*types.PrecommitMessage, 0, count) + + for index := 0; index < count; index++ { + message := &types.PrecommitMessage{ + Sender: []byte(strconv.Itoa(index)), + View: view, + } + + messages = append(messages, message) + } + + return messages +} + +func TestCollector_AddMessage(t *testing.T) { + t.Parallel() + + t.Run("empty message queue", func(t *testing.T) { + t.Parallel() + + // Create the collector + c := NewCollector[types.ProposalMessage]() + + // Fetch the messages + messages := c.GetMessages() + + require.NotNil(t, messages) + assert.Len(t, messages, 0) + }) + + t.Run("valid PROPOSAL messages fetched", func(t *testing.T) { + t.Parallel() + + var ( + count = 5 + initialView = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.ProposalMessage]() + + generatedMessages := generateProposalMessages( + t, + count, + initialView, + ) + + expectedMessages := make([]*types.ProposalMessage, 0, count) + + for _, proposal := range generatedMessages { + c.AddMessage(proposal.View, proposal.Sender, proposal) + + expectedMessages = append(expectedMessages, proposal) + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Get the messages Sender the store + messages := c.GetMessages() + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) + + t.Run("valid PREVOTE messages fetched", func(t *testing.T) { + t.Parallel() + + var ( + count = 5 + initialView = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + initialView, + ) + + expectedMessages := make([]*types.PrevoteMessage, 0, count) + + for _, prevote := range generatedMessages { + c.AddMessage(prevote.View, prevote.Sender, prevote) + + expectedMessages = append(expectedMessages, prevote) + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Get the messages from the store + messages := c.GetMessages() + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) + + t.Run("valid PRECOMMIT messages fetched", func(t *testing.T) { + t.Parallel() + + var ( + count = 5 + initialView = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrecommitMessage]() + + generatedMessages := generatePrecommitMessages( + t, + count, + initialView, + ) + + expectedMessages := make([]*types.PrecommitMessage, 0, count) + + for _, precommit := range generatedMessages { + c.AddMessage(precommit.View, precommit.Sender, precommit) + + expectedMessages = append(expectedMessages, precommit) + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Get the messages Sender the store + messages := c.GetMessages() + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) +} + +func TestCollector_AddDuplicateMessages(t *testing.T) { + t.Parallel() + + var ( + count = 5 + commonSender = []byte("sender 1") + view = &types.View{ + Height: 1, + Round: 1, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + view, + ) + + for _, prevote := range generatedMessages { + // Make sure each message is from the same sender + prevote.Sender = commonSender + + c.AddMessage(prevote.View, prevote.Sender, prevote) + } + + // Check that only 1 message has been added + assert.Len(t, c.GetMessages(), 1) +} + +func TestCollector_Subscribe(t *testing.T) { + t.Parallel() + + t.Run("subscribe with pre-existing messages", func(t *testing.T) { + t.Parallel() + + var ( + count = 100 + view = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + view, + ) + + expectedMessages := make([]*types.PrevoteMessage, 0, count) + + for _, prevote := range generatedMessages { + c.AddMessage(prevote.View, prevote.Sender, prevote) + + expectedMessages = append(expectedMessages, prevote) + } + + // Create a subscription + notifyCh, unsubscribeFn := c.Subscribe() + defer unsubscribeFn() + + var messages []*types.PrevoteMessage + + select { + case callback := <-notifyCh: + messages = callback() + case <-time.After(5 * time.Second): + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) + + t.Run("subscribe with no pre-existing messages", func(t *testing.T) { + t.Parallel() + + var ( + count = 100 + view = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + view, + ) + + expectedMessages := make([]*types.PrevoteMessage, 0, count) + + // Create a subscription + notifyCh, unsubscribeFn := c.Subscribe() + defer unsubscribeFn() + + for _, prevote := range generatedMessages { + c.AddMessage(prevote.View, prevote.Sender, prevote) + + expectedMessages = append(expectedMessages, prevote) + } + + var ( + messages []*types.PrevoteMessage + + wg sync.WaitGroup + ) + + wg.Add(1) + + go func() { + defer wg.Done() + + select { + case callback := <-notifyCh: + messages = callback() + case <-time.After(5 * time.Second): + } + }() + + wg.Wait() + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) +} + +func TestCollector_DropMessages(t *testing.T) { + t.Parallel() + + var ( + count = 5 + view = &types.View{ + Height: 10, + Round: 5, + } + earlierView = &types.View{ + Height: view.Height, + Round: view.Round - 1, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + // Generate latest round messages + latestRoundMessages := generatePrevoteMessages( + t, + count, + view, + ) + + // Generate earlier round messages + earlierRoundMessages := generatePrevoteMessages( + t, + count, + earlierView, + ) + + for _, message := range latestRoundMessages { + c.AddMessage(message.GetView(), message.GetSender(), message) + } + + for _, message := range earlierRoundMessages { + c.AddMessage(message.GetView(), message.GetSender(), message) + } + + // Drop the older messages + c.DropMessages(view) + + // Make sure the messages were dropped + fetchedMessages := c.GetMessages() + + require.Len(t, fetchedMessages, len(latestRoundMessages)) + assert.ElementsMatch(t, fetchedMessages, latestRoundMessages) +} diff --git a/tm2/pkg/libtm/messages/subscription.go b/tm2/pkg/libtm/messages/subscription.go new file mode 100644 index 00000000000..c3d3c23333d --- /dev/null +++ b/tm2/pkg/libtm/messages/subscription.go @@ -0,0 +1,57 @@ +package messages + +import "github.com/rs/xid" + +type ( + // MsgCallback is the callback that returns all given messages + MsgCallback[T msgType] func() []*T + + // subscriptions is the subscription store, + // maps subscription id -> notification channel. + // Usage of this type is NOT thread safe + subscriptions[T msgType] map[string]chan func() []*T +) + +// add adds a new subscription to the subscription map. +// Returns the subscription ID, and update channel +func (s *subscriptions[T]) add() (string, chan func() []*T) { + var ( + id = xid.New().String() + ch = make(chan func() []*T, 1) + ) + + (*s)[id] = ch + + return id, ch +} + +// remove removes the given subscription +func (s *subscriptions[T]) remove(id string) { + if ch := (*s)[id]; ch != nil { + // Close the notification channel + close(ch) + } + + // Delete the subscription + delete(*s, id) +} + +// notify notifies all subscription listeners +func (s *subscriptions[T]) notify(callback func() []*T) { + // Notify the listeners + for _, ch := range *s { + notifySubscription(ch, callback) + } +} + +// notifySubscription alerts the notification channel +// about a callback. This function is pure syntactic sugar +func notifySubscription[T msgType]( + ch chan func() []*T, + callback MsgCallback[T], +) { + select { + case ch <- callback: + default: + } +} diff --git a/tm2/pkg/libtm/messages/types/messages.go b/tm2/pkg/libtm/messages/types/messages.go new file mode 100644 index 00000000000..d43e0be8a27 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/messages.go @@ -0,0 +1,208 @@ +package types + +import ( + "bytes" + "errors" + + "google.golang.org/protobuf/proto" +) + +var ( + ErrInvalidMessageView = errors.New("invalid message view") + ErrInvalidMessageSender = errors.New("invalid message sender") + ErrInvalidMessageProposal = errors.New("invalid message proposal") + ErrInvalidMessageProposalRound = errors.New("invalid message proposal round") +) + +func (v *View) Equals(view *View) bool { + if v.GetHeight() != view.GetHeight() { + return false + } + + return v.GetRound() == view.GetRound() +} + +// GetSignaturePayload returns the sign payload for the proposal message +func (m *ProposalMessage) GetSignaturePayload() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&ProposalMessage{ + View: m.View, + Sender: m.Sender, + Proposal: m.Proposal, + ProposalRound: m.ProposalRound, + }) + + return raw +} + +// Marshal returns the marshalled message +func (m *ProposalMessage) Marshal() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&ProposalMessage{ + View: m.View, + Sender: m.Sender, + Signature: m.Signature, + Proposal: m.Proposal, + ProposalRound: m.ProposalRound, + }) + + return raw +} + +// Verify validates that the given message is valid +func (m *ProposalMessage) Verify() error { + // Make sure the view is present + if m.View == nil { + return ErrInvalidMessageView + } + + // Make sure the sender is present + if m.Sender == nil { + return ErrInvalidMessageSender + } + + // Make sure the proposal is present + if m.Proposal == nil { + return ErrInvalidMessageProposal + } + + // Make sure the proposal round is + // for a good round value + if m.ProposalRound < -1 { + return ErrInvalidMessageProposalRound + } + + return nil +} + +func (m *ProposalMessage) Equals(message *ProposalMessage) bool { + if !m.GetView().Equals(message.GetView()) { + return false + } + + if !bytes.Equal(m.GetSender(), message.GetSender()) { + return false + } + + if !bytes.Equal(m.GetSignature(), message.GetSignature()) { + return false + } + + if !bytes.Equal(m.GetProposal(), message.GetProposal()) { + return false + } + + return m.GetProposalRound() == message.GetProposalRound() +} + +// GetSignaturePayload returns the sign payload for the proposal message +func (m *PrevoteMessage) GetSignaturePayload() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrevoteMessage{ + View: m.View, + Sender: m.Sender, + Identifier: m.Identifier, + }) + + return raw +} + +// Marshal returns the marshalled message +func (m *PrevoteMessage) Marshal() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrevoteMessage{ + View: m.View, + Sender: m.Sender, + Signature: m.Signature, + Identifier: m.Identifier, + }) + + return raw +} + +// Verify validates that the given message is valid +func (m *PrevoteMessage) Verify() error { + // Make sure the view is present + if m.View == nil { + return ErrInvalidMessageView + } + + // Make sure the sender is present + if m.Sender == nil { + return ErrInvalidMessageSender + } + + return nil +} + +func (m *PrevoteMessage) Equals(message *PrevoteMessage) bool { + if !m.GetView().Equals(message.GetView()) { + return false + } + + if !bytes.Equal(m.GetSender(), message.GetSender()) { + return false + } + + if !bytes.Equal(m.GetSignature(), message.GetSignature()) { + return false + } + + return bytes.Equal(m.GetIdentifier(), message.GetIdentifier()) +} + +// GetSignaturePayload returns the sign payload for the proposal message +func (m *PrecommitMessage) GetSignaturePayload() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrecommitMessage{ + View: m.View, + Sender: m.Sender, + Identifier: m.Identifier, + }) + + return raw +} + +// Marshal returns the marshalled message +func (m *PrecommitMessage) Marshal() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrecommitMessage{ + View: m.View, + Sender: m.Sender, + Signature: m.Signature, + Identifier: m.Identifier, + }) + + return raw +} + +// Verify validates that the given message is valid +func (m *PrecommitMessage) Verify() error { + // Make sure the view is present + if m.View == nil { + return ErrInvalidMessageView + } + + // Make sure the sender is present + if m.Sender == nil { + return ErrInvalidMessageSender + } + + return nil +} + +func (m *PrecommitMessage) Equals(message *PrecommitMessage) bool { + if !m.GetView().Equals(message.GetView()) { + return false + } + + if !bytes.Equal(m.GetSender(), message.GetSender()) { + return false + } + + if !bytes.Equal(m.GetSignature(), message.GetSignature()) { + return false + } + + return bytes.Equal(m.GetIdentifier(), message.GetIdentifier()) +} diff --git a/tm2/pkg/libtm/messages/types/messages.pb.go b/tm2/pkg/libtm/messages/types/messages.pb.go new file mode 100644 index 00000000000..5e09d2a1a11 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/messages.pb.go @@ -0,0 +1,543 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.3 +// source: messages/types/proto/messages.proto + +package types + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// MessageType defines the types of messages +// that are related to the consensus process +type MessageType int32 + +const ( + MessageType_PROPOSAL MessageType = 0 + MessageType_PREVOTE MessageType = 1 + MessageType_PRECOMMIT MessageType = 2 +) + +// Enum value maps for MessageType. +var ( + MessageType_name = map[int32]string{ + 0: "PROPOSAL", + 1: "PREVOTE", + 2: "PRECOMMIT", + } + MessageType_value = map[string]int32{ + "PROPOSAL": 0, + "PREVOTE": 1, + "PRECOMMIT": 2, + } +) + +func (x MessageType) Enum() *MessageType { + p := new(MessageType) + *p = x + return p +} + +func (x MessageType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MessageType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_types_proto_messages_proto_enumTypes[0].Descriptor() +} + +func (MessageType) Type() protoreflect.EnumType { + return &file_messages_types_proto_messages_proto_enumTypes[0] +} + +func (x MessageType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MessageType.Descriptor instead. +func (MessageType) EnumDescriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{0} +} + +// View is the consensus state associated with the message +type View struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // height represents the number of the proposal + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // round represents the round number within a + // specific height (starts from 0) + Round uint64 `protobuf:"varint,2,opt,name=round,proto3" json:"round,omitempty"` +} + +func (x *View) Reset() { + *x = View{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *View) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*View) ProtoMessage() {} + +func (x *View) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use View.ProtoReflect.Descriptor instead. +func (*View) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{0} +} + +func (x *View) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *View) GetRound() uint64 { + if x != nil { + return x.Round + } + return 0 +} + +// ProposalMessage is the message containing +// the consensus proposal for the view +// +type ProposalMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // view is the current view for the message + // (the view in which the message was sent) + View *View `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` + // sender is the message sender (unique identifier) + Sender []byte `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + // signature is the message signature of the sender + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + // proposal is the actual consensus proposal + Proposal []byte `protobuf:"bytes,4,opt,name=proposal,proto3" json:"proposal,omitempty"` + // proposalRound is the round associated with the + // proposal in the PROPOSE message. + // NOTE: this round value DOES NOT have + // to match the message view (proposal from an earlier round) + ProposalRound int64 `protobuf:"varint,5,opt,name=proposalRound,proto3" json:"proposalRound,omitempty"` +} + +func (x *ProposalMessage) Reset() { + *x = ProposalMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProposalMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProposalMessage) ProtoMessage() {} + +func (x *ProposalMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProposalMessage.ProtoReflect.Descriptor instead. +func (*ProposalMessage) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{1} +} + +func (x *ProposalMessage) GetView() *View { + if x != nil { + return x.View + } + return nil +} + +func (x *ProposalMessage) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *ProposalMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *ProposalMessage) GetProposal() []byte { + if x != nil { + return x.Proposal + } + return nil +} + +func (x *ProposalMessage) GetProposalRound() int64 { + if x != nil { + return x.ProposalRound + } + return 0 +} + +// PrevoteMessage is the message +// containing the consensus proposal prevote. +// The prevote message is pretty light, +// apart from containing the view, it just +// contains a unique identifier of the proposal +// for which this prevote is meant for (ex. proposal hash) +// +type PrevoteMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // view is the current view for the message + // (the view in which the message was sent) + View *View `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` + // sender is the message sender (unique identifier) + Sender []byte `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + // signature is the message signature of the sender + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + // identifier is the unique identifier for + // the proposal associated with this + // prevote message (ex. proposal hash) + Identifier []byte `protobuf:"bytes,4,opt,name=identifier,proto3" json:"identifier,omitempty"` +} + +func (x *PrevoteMessage) Reset() { + *x = PrevoteMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrevoteMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrevoteMessage) ProtoMessage() {} + +func (x *PrevoteMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrevoteMessage.ProtoReflect.Descriptor instead. +func (*PrevoteMessage) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{2} +} + +func (x *PrevoteMessage) GetView() *View { + if x != nil { + return x.View + } + return nil +} + +func (x *PrevoteMessage) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *PrevoteMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *PrevoteMessage) GetIdentifier() []byte { + if x != nil { + return x.Identifier + } + return nil +} + +// PrecommitMessage is the message +// containing the consensus proposal precommit. +// The precommit message, same as the prevote message, +// contains a unique identifier for the proposal +// for which this precommit is meant for (ex. proposal hash) +// +type PrecommitMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // view is the current view for the message + // (the view in which the message was sent) + View *View `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` + // sender is the message sender (unique identifier) + Sender []byte `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + // signature is the message signature of the sender + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + // identifier is the unique identifier for + // the proposal associated with this + // precommit message (ex. proposal hash) + Identifier []byte `protobuf:"bytes,4,opt,name=identifier,proto3" json:"identifier,omitempty"` +} + +func (x *PrecommitMessage) Reset() { + *x = PrecommitMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrecommitMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrecommitMessage) ProtoMessage() {} + +func (x *PrecommitMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrecommitMessage.ProtoReflect.Descriptor instead. +func (*PrecommitMessage) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{3} +} + +func (x *PrecommitMessage) GetView() *View { + if x != nil { + return x.View + } + return nil +} + +func (x *PrecommitMessage) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *PrecommitMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *PrecommitMessage) GetIdentifier() []byte { + if x != nil { + return x.Identifier + } + return nil +} + +var File_messages_types_proto_messages_proto protoreflect.FileDescriptor + +var file_messages_types_proto_messages_proto_rawDesc = []byte{ + 0x0a, 0x23, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, 0x04, 0x56, 0x69, 0x65, 0x77, 0x12, 0x16, 0x0a, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xa4, 0x01, 0x0a, 0x0f, + 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x19, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, + 0x56, 0x69, 0x65, 0x77, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0d, + 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x65, 0x76, 0x6f, 0x74, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x56, 0x69, 0x65, 0x77, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x50, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x76, + 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x56, 0x69, 0x65, 0x77, + 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2a, 0x37, 0x0a, 0x0b, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x50, + 0x52, 0x4f, 0x50, 0x4f, 0x53, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x45, + 0x56, 0x4f, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, + 0x4d, 0x49, 0x54, 0x10, 0x02, 0x42, 0x11, 0x5a, 0x0f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_messages_types_proto_messages_proto_rawDescOnce sync.Once + file_messages_types_proto_messages_proto_rawDescData = file_messages_types_proto_messages_proto_rawDesc +) + +func file_messages_types_proto_messages_proto_rawDescGZIP() []byte { + file_messages_types_proto_messages_proto_rawDescOnce.Do(func() { + file_messages_types_proto_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_types_proto_messages_proto_rawDescData) + }) + return file_messages_types_proto_messages_proto_rawDescData +} + +var file_messages_types_proto_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_messages_types_proto_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_messages_types_proto_messages_proto_goTypes = []interface{}{ + (MessageType)(0), // 0: MessageType + (*View)(nil), // 1: View + (*ProposalMessage)(nil), // 2: ProposalMessage + (*PrevoteMessage)(nil), // 3: PrevoteMessage + (*PrecommitMessage)(nil), // 4: PrecommitMessage +} +var file_messages_types_proto_messages_proto_depIdxs = []int32{ + 1, // 0: ProposalMessage.view:type_name -> View + 1, // 1: PrevoteMessage.view:type_name -> View + 1, // 2: PrecommitMessage.view:type_name -> View + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_messages_types_proto_messages_proto_init() } +func file_messages_types_proto_messages_proto_init() { + if File_messages_types_proto_messages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_messages_types_proto_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*View); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_types_proto_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProposalMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_types_proto_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrevoteMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_types_proto_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrecommitMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_types_proto_messages_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_messages_types_proto_messages_proto_goTypes, + DependencyIndexes: file_messages_types_proto_messages_proto_depIdxs, + EnumInfos: file_messages_types_proto_messages_proto_enumTypes, + MessageInfos: file_messages_types_proto_messages_proto_msgTypes, + }.Build() + File_messages_types_proto_messages_proto = out.File + file_messages_types_proto_messages_proto_rawDesc = nil + file_messages_types_proto_messages_proto_goTypes = nil + file_messages_types_proto_messages_proto_depIdxs = nil +} diff --git a/tm2/pkg/libtm/messages/types/messages_test.go b/tm2/pkg/libtm/messages/types/messages_test.go new file mode 100644 index 00000000000..516b5436098 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/messages_test.go @@ -0,0 +1,872 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestProposalMessage_GetSignaturePayload(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &ProposalMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + Proposal: []byte("proposal"), + ProposalRound: 0, + } + + // Get the signature payload + payload := m.GetSignaturePayload() + + var raw ProposalMessage + + require.NoError(t, proto.Unmarshal(payload, &raw)) + + // Make sure the signature was not marshalled + assert.Nil(t, raw.Signature) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetProposal(), raw.GetProposal()) + assert.Equal(t, m.GetProposalRound(), raw.GetProposalRound()) +} + +func TestProposalMessage_Marshal(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &ProposalMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + Proposal: []byte("proposal"), + ProposalRound: 0, + } + + // Marshal the message + marshalled := m.Marshal() + + var raw ProposalMessage + + require.NoError(t, proto.Unmarshal(marshalled, &raw)) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetSignature(), raw.GetSignature()) + assert.Equal(t, m.GetProposal(), raw.GetProposal()) + assert.Equal(t, m.GetProposalRound(), raw.GetProposalRound()) +} + +func TestProposalMessage_Verify(t *testing.T) { + t.Parallel() + + t.Run("invalid view", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageView) + }) + + t.Run("invalid sender", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{}, + Sender: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageSender) + }) + + t.Run("invalid proposal", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{}, + Sender: []byte{}, + Proposal: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageProposal) + }) + + t.Run("invalid proposal round", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{}, + Sender: []byte{}, + Proposal: []byte{}, + ProposalRound: -2, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageProposalRound) + }) + + t.Run("valid proposal message", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{ + Height: 1, + Round: 0, + }, + Sender: []byte("sender"), + Proposal: []byte("proposal"), + ProposalRound: -1, + } + + assert.NoError(t, m.Verify()) + }) +} + +func TestPrevoteMessage_GetSignaturePayload(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrevoteMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Get the signature payload + payload := m.GetSignaturePayload() + + var raw PrevoteMessage + + require.NoError(t, proto.Unmarshal(payload, &raw)) + + // Make sure the signature was not marshalled + assert.Nil(t, raw.Signature) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) +} + +func TestPrevoteMessage_Marshal(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrevoteMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Marshal the message + marshalled := m.Marshal() + + var raw PrevoteMessage + + require.NoError(t, proto.Unmarshal(marshalled, &raw)) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetSignature(), raw.GetSignature()) +} + +func TestPrevoteMessage_Verify(t *testing.T) { + t.Parallel() + + t.Run("invalid view", func(t *testing.T) { + t.Parallel() + + m := &PrevoteMessage{ + View: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageView) + }) + + t.Run("invalid sender", func(t *testing.T) { + t.Parallel() + + m := &PrevoteMessage{ + View: &View{}, + Sender: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageSender) + }) +} + +func TestPrecommitMessage_GetSignaturePayload(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrecommitMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Get the signature payload + payload := m.GetSignaturePayload() + + var raw PrecommitMessage + + require.NoError(t, proto.Unmarshal(payload, &raw)) + + // Make sure the signature was not marshalled + assert.Nil(t, raw.Signature) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) +} + +func TestPrecommitMessage_Marshal(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrecommitMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Marshal the message + marshalled := m.Marshal() + + var raw PrecommitMessage + + require.NoError(t, proto.Unmarshal(marshalled, &raw)) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetSignature(), raw.GetSignature()) +} + +func TestPrecommitMessage_Verify(t *testing.T) { + t.Parallel() + + t.Run("invalid view", func(t *testing.T) { + t.Parallel() + + m := &PrecommitMessage{ + View: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageView) + }) + + t.Run("invalid sender", func(t *testing.T) { + t.Parallel() + + m := &PrecommitMessage{ + View: &View{}, + Sender: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageSender) + }) +} + +func TestView_Equals(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + views []*View + shouldEqual bool + }{ + { + "equal views", + []*View{ + { + Height: 10, + Round: 10, + }, + { + Height: 10, + Round: 10, + }, + }, + true, + }, + { + "not equal views", + []*View{ + { + Height: 10, + Round: 10, + }, + { + Height: 10, + Round: 5, // different round + }, + }, + false, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + assert.Equal( + t, + testCase.shouldEqual, + testCase.views[0].Equals(testCase.views[1]), + ) + }) + } +} + +func TestProposalMessage_Equals(t *testing.T) { + t.Parallel() + + t.Run("equal proposal messages", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.True(t, left.Equals(right)) + }) + + t.Run("view mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: &View{ + Height: view.Height, + Round: view.Round + 1, // round mismatch + }, Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("sender mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: []byte("different sender"), // sender mismatch + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("signature mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: []byte("different signature"), // signature mismatch + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("proposal mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: []byte("different proposal"), // proposal mismatch + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("proposal round mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound + 1, // proposal round mismatch + } + ) + + assert.False(t, left.Equals(right)) + }) +} + +func TestPrevoteMessage_Equals(t *testing.T) { + t.Parallel() + + t.Run("equal prevote messages", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.True(t, left.Equals(right)) + }) + + t.Run("view mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: &View{ + Height: view.Height, + Round: view.Round + 1, // round mismatch + }, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("sender mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: []byte("different sender"), // sender mismatch + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("signature mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: []byte("different signature"), // signature mismatch + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("identifier mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: []byte("different identifier"), // identifier mismatch + } + ) + + assert.False(t, left.Equals(right)) + }) +} + +func TestPrecommitMessage_Equals(t *testing.T) { + t.Parallel() + + t.Run("equal precommit messages", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.True(t, left.Equals(right)) + }) + + t.Run("view mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: &View{ + Height: view.Height, + Round: view.Round + 1, // round mismatch + }, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("sender mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: []byte("different sender"), // sender mismatch + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("signature mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: []byte("different signature"), // signature mismatch + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("identifier mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: []byte("different identifier"), // identifier mismatch + } + ) + + assert.False(t, left.Equals(right)) + }) +} diff --git a/tm2/pkg/libtm/messages/types/proto/messages.proto b/tm2/pkg/libtm/messages/types/proto/messages.proto new file mode 100644 index 00000000000..b41c0e5efe8 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/proto/messages.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; + +option go_package = "/messages/types"; + +// MessageType defines the types of messages +// that are related to the consensus process +enum MessageType { + PROPOSAL = 0; + PREVOTE = 1; + PRECOMMIT = 2; +} + +// View is the consensus state associated with the message +message View { + // height represents the number of the proposal + uint64 height = 1; + + // round represents the round number within a + // specific height (starts from 0) + uint64 round = 2; +} + +// ProposalMessage is the message containing +// the consensus proposal for the view +// +message ProposalMessage { + // view is the current view for the message + // (the view in which the message was sent) + View view = 1; + + // sender is the message sender (unique identifier) + bytes sender = 2; + + // signature is the message signature of the sender + bytes signature = 3; + + // proposal is the actual consensus proposal + bytes proposal = 4; + + // proposalRound is the round associated with the + // proposal in the PROPOSE message. + // NOTE: this round value DOES NOT have + // to match the message view (proposal from an earlier round) + int64 proposalRound = 5; +} + +// PrevoteMessage is the message +// containing the consensus proposal prevote. +// The prevote message is pretty light, +// apart from containing the view, it just +// contains a unique identifier of the proposal +// for which this prevote is meant for (ex. proposal hash) +// +message PrevoteMessage { + // view is the current view for the message + // (the view in which the message was sent) + View view = 1; + + // sender is the message sender (unique identifier) + bytes sender = 2; + + // signature is the message signature of the sender + bytes signature = 3; + + // identifier is the unique identifier for + // the proposal associated with this + // prevote message (ex. proposal hash) + bytes identifier = 4; +} + +// PrecommitMessage is the message +// containing the consensus proposal precommit. +// The precommit message, same as the prevote message, +// contains a unique identifier for the proposal +// for which this precommit is meant for (ex. proposal hash) +// +message PrecommitMessage { + // view is the current view for the message + // (the view in which the message was sent) + View view = 1; + + // sender is the message sender (unique identifier) + bytes sender = 2; + + // signature is the message signature of the sender + bytes signature = 3; + + // identifier is the unique identifier for + // the proposal associated with this + // precommit message (ex. proposal hash) + bytes identifier = 4; +} \ No newline at end of file diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index a0ed2d39381..07692145fee 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -18,67 +18,67 @@ const ( // P2PConfig defines the configuration options for the Tendermint peer-to-peer networking layer type P2PConfig struct { - RootDir string `toml:"home"` + RootDir string `json:"rpc" toml:"home"` // Address to listen for incoming connections - ListenAddress string `toml:"laddr" comment:"Address to listen for incoming connections"` + ListenAddress string `json:"laddr" toml:"laddr" comment:"Address to listen for incoming connections"` // Address to advertise to peers for them to dial - ExternalAddress string `toml:"external_address" comment:"Address to advertise to peers for them to dial\n If empty, will use the same port as the laddr,\n and will introspect on the listener or use UPnP\n to figure out the address."` + ExternalAddress string `json:"external_address" toml:"external_address" comment:"Address to advertise to peers for them to dial\n If empty, will use the same port as the laddr,\n and will introspect on the listener or use UPnP\n to figure out the address."` // Comma separated list of seed nodes to connect to - Seeds string `toml:"seeds" comment:"Comma separated list of seed nodes to connect to"` + Seeds string `json:"seeds" toml:"seeds" comment:"Comma separated list of seed nodes to connect to"` // Comma separated list of nodes to keep persistent connections to - PersistentPeers string `toml:"persistent_peers" comment:"Comma separated list of nodes to keep persistent connections to"` + PersistentPeers string `json:"persistent_peers" toml:"persistent_peers" comment:"Comma separated list of nodes to keep persistent connections to"` // UPNP port forwarding - UPNP bool `toml:"upnp" comment:"UPNP port forwarding"` + UPNP bool `json:"upnp" toml:"upnp" comment:"UPNP port forwarding"` // Maximum number of inbound peers - MaxNumInboundPeers int `toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` + MaxNumInboundPeers int `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` // Maximum number of outbound peers to connect to, excluding persistent peers - MaxNumOutboundPeers int `toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` + MaxNumOutboundPeers int `json:"max_num_outbound_peers" toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` // Time to wait before flushing messages out on the connection - FlushThrottleTimeout time.Duration `toml:"flush_throttle_timeout" comment:"Time to wait before flushing messages out on the connection"` + FlushThrottleTimeout time.Duration `json:"flush_throttle_timeout" toml:"flush_throttle_timeout" comment:"Time to wait before flushing messages out on the connection"` // Maximum size of a message packet payload, in bytes - MaxPacketMsgPayloadSize int `toml:"max_packet_msg_payload_size" comment:"Maximum size of a message packet payload, in bytes"` + MaxPacketMsgPayloadSize int `json:"max_packet_msg_payload_size" toml:"max_packet_msg_payload_size" comment:"Maximum size of a message packet payload, in bytes"` // Rate at which packets can be sent, in bytes/second - SendRate int64 `toml:"send_rate" comment:"Rate at which packets can be sent, in bytes/second"` + SendRate int64 `json:"send_rate" toml:"send_rate" comment:"Rate at which packets can be sent, in bytes/second"` // Rate at which packets can be received, in bytes/second - RecvRate int64 `toml:"recv_rate" comment:"Rate at which packets can be received, in bytes/second"` + RecvRate int64 `json:"recv_rate" toml:"recv_rate" comment:"Rate at which packets can be received, in bytes/second"` // Set true to enable the peer-exchange reactor - PexReactor bool `toml:"pex" comment:"Set true to enable the peer-exchange reactor"` + PexReactor bool `json:"pex" toml:"pex" comment:"Set true to enable the peer-exchange reactor"` // Seed mode, in which node constantly crawls the network and looks for // peers. If another node asks it for addresses, it responds and disconnects. // // Does not work if the peer-exchange reactor is disabled. - SeedMode bool `toml:"seed_mode" comment:"Seed mode, in which node constantly crawls the network and looks for\n peers. If another node asks it for addresses, it responds and disconnects.\n\n Does not work if the peer-exchange reactor is disabled."` + SeedMode bool `json:"seed_mode" toml:"seed_mode" comment:"Seed mode, in which node constantly crawls the network and looks for\n peers. If another node asks it for addresses, it responds and disconnects.\n\n Does not work if the peer-exchange reactor is disabled."` // Comma separated list of peer IDs to keep private (will not be gossiped to // other peers) - PrivatePeerIDs string `toml:"private_peer_ids" comment:"Comma separated list of peer IDs to keep private (will not be gossiped to other peers)"` + PrivatePeerIDs string `json:"private_peer_ids" toml:"private_peer_ids" comment:"Comma separated list of peer IDs to keep private (will not be gossiped to other peers)"` // Toggle to disable guard against peers connecting from the same ip. - AllowDuplicateIP bool `toml:"allow_duplicate_ip" comment:"Toggle to disable guard against peers connecting from the same ip."` + AllowDuplicateIP bool `json:"allow_duplicate_ip" toml:"allow_duplicate_ip" comment:"Toggle to disable guard against peers connecting from the same ip."` // Peer connection configuration. - HandshakeTimeout time.Duration `toml:"handshake_timeout" comment:"Peer connection configuration."` - DialTimeout time.Duration `toml:"dial_timeout"` + HandshakeTimeout time.Duration `json:"handshake_timeout" toml:"handshake_timeout" comment:"Peer connection configuration."` + DialTimeout time.Duration `json:"dial_timeout" toml:"dial_timeout"` // Testing params. // Force dial to fail - TestDialFail bool `toml:"test_dial_fail"` + TestDialFail bool `json:"test_dial_fail" toml:"test_dial_fail"` // FUzz connection - TestFuzz bool `toml:"test_fuzz"` - TestFuzzConfig *FuzzConnConfig `toml:"test_fuzz_config"` + TestFuzz bool `json:"test_fuzz" toml:"test_fuzz"` + TestFuzzConfig *FuzzConnConfig `json:"test_fuzz_config" toml:"test_fuzz_config"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer @@ -107,7 +107,7 @@ func DefaultP2PConfig() *P2PConfig { // TestP2PConfig returns a configuration for testing the peer-to-peer layer func TestP2PConfig() *P2PConfig { cfg := DefaultP2PConfig() - cfg.ListenAddress = "tcp://0.0.0.0:36656" + cfg.ListenAddress = "tcp://0.0.0.0:26656" cfg.FlushThrottleTimeout = 10 * time.Millisecond cfg.AllowDuplicateIP = true return cfg diff --git a/tm2/pkg/p2p/fuzz.go b/tm2/pkg/p2p/fuzz.go index f41189d409e..03cf88cf750 100644 --- a/tm2/pkg/p2p/fuzz.go +++ b/tm2/pkg/p2p/fuzz.go @@ -21,28 +21,6 @@ type FuzzedConnection struct { config *config.FuzzConnConfig } -// FuzzConn creates a new FuzzedConnection. Fuzzing starts immediately. -func FuzzConn(conn net.Conn) net.Conn { - return FuzzConnFromConfig(conn, config.DefaultFuzzConnConfig()) -} - -// FuzzConnFromConfig creates a new FuzzedConnection from a config. Fuzzing -// starts immediately. -func FuzzConnFromConfig(conn net.Conn, config *config.FuzzConnConfig) net.Conn { - return &FuzzedConnection{ - conn: conn, - start: make(<-chan time.Time), - active: true, - config: config, - } -} - -// FuzzConnAfter creates a new FuzzedConnection. Fuzzing starts when the -// duration elapses. -func FuzzConnAfter(conn net.Conn, d time.Duration) net.Conn { - return FuzzConnAfterFromConfig(conn, d, config.DefaultFuzzConnConfig()) -} - // FuzzConnAfterFromConfig creates a new FuzzedConnection from a config. // Fuzzing starts when the duration elapses. func FuzzConnAfterFromConfig( diff --git a/tm2/pkg/p2p/key.go b/tm2/pkg/p2p/key.go index 71e3459f418..a41edeb07f8 100644 --- a/tm2/pkg/p2p/key.go +++ b/tm2/pkg/p2p/key.go @@ -17,6 +17,7 @@ import ( // NodeKey is the persistent peer key. // It contains the nodes private key for authentication. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go type NodeKey struct { crypto.PrivKey `json:"priv_key"` // our priv key } diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index ac24a2a45b7..cecfc21f3ef 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -717,5 +717,29 @@ func (sw *Switch) addPeer(p Peer) error { sw.Logger.Info("Added peer", "peer", p) + // Update the telemetry data + sw.logTelemetry() + return nil } + +// logTelemetry logs the switch telemetry data +// to global metrics funnels +func (sw *Switch) logTelemetry() { + // Update the telemetry data + if !telemetry.MetricsEnabled() { + return + } + + // Fetch the number of peers + outbound, inbound, dialing := sw.NumPeers() + + // Log the outbound peer count + metrics.OutboundPeers.Record(context.Background(), int64(outbound)) + + // Log the inbound peer count + metrics.InboundPeers.Record(context.Background(), int64(inbound)) + + // Log the dialing peer count + metrics.DialingPeers.Record(context.Background(), int64(dialing)) +} diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index 3e7abbc6c6f..dd0d9cd6bc7 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -19,24 +19,6 @@ const testCh = 0x01 // ------------------------------------------------ -func AddPeerToSwitchPeerSet(sw *Switch, peer Peer) { - sw.peers.Add(peer) -} - -func CreateRandomPeer(outbound bool) *peer { - addr, netAddr := CreateRoutableAddr() - p := &peer{ - peerConn: peerConn{ - outbound: outbound, - socketAddr: netAddr, - }, - nodeInfo: NodeInfo{NetAddress: netAddr}, - mconn: &conn.MConnection{}, - } - p.SetLogger(log.NewNoopLogger().With("peer", addr)) - return p -} - func CreateRoutableAddr() (addr string, netAddr *NetAddress) { for { id := ed25519.GenPrivKey().PubKey().Address().ID() diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index 5066a7b1fde..bfaa63c9475 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -139,11 +139,19 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature stdSigs := tx.GetSignatures() for i := 0; i < len(stdSigs); i++ { + var isNewAccount bool // skip the fee payer, account is cached and fees were deducted already if i != 0 { signerAccs[i], res = GetSignerAcc(newCtx, ak, signerAddrs[i]) + // only create new account when tx is a sponsor transaction if !res.IsOK() { - return newCtx, res, true + if tx.IsSponsorTx() { + isNewAccount = true + signerAccs[i] = ak.NewAccountWithAddress(newCtx, signerAddrs[i]) + signerAccs[i].SetPubKey(stdSigs[i].PubKey) + } else { + return newCtx, res, true + } } } @@ -153,11 +161,10 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature // No signatures are needed for genesis. } else { // Check signature - signBytes, err := GetSignBytes(newCtx.ChainID(), tx, sacc, isGenesis) + signBytes, err := GetSignBytes(newCtx.ChainID(), tx, sacc, isGenesis || isNewAccount) if err != nil { return newCtx, res, true } - signerAccs[i], res = processSig(newCtx, sacc, stdSigs[i], signBytes, simulate, params, sigGasConsumer) if !res.IsOK() { return newCtx, res, true diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index 1801a0af35f..0fa26b817e1 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -209,14 +209,6 @@ func (app *BaseApp) setMinGasPrices(gasPrices []GasPrice) { app.minGasPrices = gasPrices } -func (app *BaseApp) setHaltHeight(haltHeight uint64) { - app.haltHeight = haltHeight -} - -func (app *BaseApp) setHaltTime(haltTime uint64) { - app.haltTime = haltTime -} - // Returns a read-only (cache) MultiStore. // This may be used by keepers for initialization upon restart. func (app *BaseApp) GetCacheMultiStore() store.MultiStore { @@ -615,6 +607,9 @@ func (app *BaseApp) getContextForTx(mode RunTxMode, txBytes []byte) (ctx Context WithVoteInfos(app.voteInfos). WithConsensusParams(app.consensusParams) + // NOTE: This is especially required to simulate transactions because + // otherwise baseapp writes the antehandler mods (sequence and balance) + // to the underlying store for deliver and checktx. if mode == RunTxModeSimulate { ctx, _ = ctx.CacheContext() } @@ -624,10 +619,13 @@ func (app *BaseApp) getContextForTx(mode RunTxMode, txBytes []byte) (ctx Context // / runMsgs iterates through all the messages and executes them. func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Result) { + ctx = ctx.WithEventLogger(NewEventLogger()) + msgLogs := make([]string, 0, len(msgs)) data := make([]byte, 0, len(msgs)) err := error(nil) + events := []Event{} // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. @@ -641,23 +639,17 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res } var msgResult Result - ctx = ctx.WithEventLogger(NewEventLogger()) // run the message! // skip actual execution for CheckTx mode if mode != RunTxModeCheck { - msgResult = handler.Process(ctx, msg) + msgResult = handler.Process(ctx, msg) // ctx event logger being updated in handler } // Each message result's Data must be length prefixed in order to separate // each result. data = append(data, msgResult.Data...) events = append(events, msgResult.Events...) - defer func() { - events = append(events, ctx.EventLogger().Events()...) - result.Events = events - }() - // TODO append msgevent from ctx. XXX XXX // stop execution and return on first failed message if !msgResult.IsOK() { @@ -672,12 +664,13 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res fmt.Sprintf("msg:%d,success:%v,log:%s,events:%v", i, true, msgResult.Log, events)) } + events = append(events, ctx.EventLogger().Events()...) result.Error = ABCIError(err) result.Data = data + result.Events = events result.Log = strings.Join(msgLogs, "\n") result.GasUsed = ctx.GasMeter().GasConsumed() - result.Events = events return result } @@ -693,9 +686,7 @@ func (app *BaseApp) getState(mode RunTxMode) *state { // cacheTxContext returns a new context based off of the provided context with // a cache wrapped multi-store. -func (app *BaseApp) cacheTxContext(ctx Context, txBytes []byte) ( - Context, store.MultiStore, -) { +func (app *BaseApp) cacheTxContext(ctx Context) (Context, store.MultiStore) { ms := ctx.MultiStore() // TODO: https://github.com/tendermint/classic/sdk/issues/2824 msCache := ms.MultiCacheWrap() @@ -800,7 +791,7 @@ func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) // aborted/failed. This may have some performance // benefits, but it'll be more difficult to get // right. - anteCtx, msCache = app.cacheTxContext(ctx, txBytes) + anteCtx, msCache = app.cacheTxContext(ctx) // Call AnteHandler. // NOTE: It is the responsibility of the anteHandler // to use something like passthroughGasMeter to @@ -828,7 +819,7 @@ func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) // Create a new context based off of the existing context with a cache wrapped // multi-store in case message processing fails. - runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) + runMsgCtx, msCache := app.cacheTxContext(ctx) result = app.runMsgs(runMsgCtx, msgs, mode) result.GasWanted = gasWanted diff --git a/tm2/pkg/sdk/options.go b/tm2/pkg/sdk/options.go index 4a00681c727..f174b5501a2 100644 --- a/tm2/pkg/sdk/options.go +++ b/tm2/pkg/sdk/options.go @@ -10,11 +10,6 @@ import ( // File for storing in-package BaseApp optional functions, // for options that need access to non-exported fields of the BaseApp -// SetStoreOptions sets store options on the multistore associated with the app -func SetStoreOptions(opts store.StoreOptions) func(*BaseApp) { - return func(bap *BaseApp) { bap.cms.SetStoreOptions(opts) } -} - // SetPruningOptions sets pruning options on the multistore associated with the app func SetPruningOptions(opts store.PruningOptions) func(*BaseApp) { return func(bap *BaseApp) { @@ -34,16 +29,6 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) } } -// SetHaltHeight returns a BaseApp option function that sets the halt block height. -func SetHaltHeight(blockHeight uint64) func(*BaseApp) { - return func(bap *BaseApp) { bap.setHaltHeight(blockHeight) } -} - -// SetHaltTime returns a BaseApp option function that sets the halt block time. -func SetHaltTime(haltTime uint64) func(*BaseApp) { - return func(bap *BaseApp) { bap.setHaltTime(haltTime) } -} - func (app *BaseApp) SetName(name string) { if app.sealed { panic("SetName() on sealed BaseApp") diff --git a/tm2/pkg/sdk/sdk.proto b/tm2/pkg/sdk/sdk.proto index 828b17950cf..62fbfc19758 100644 --- a/tm2/pkg/sdk/sdk.proto +++ b/tm2/pkg/sdk/sdk.proto @@ -12,4 +12,4 @@ message Result { abci.ResponseBase response_base = 1 [json_name = "ResponseBase"]; sint64 gas_wanted = 2 [json_name = "GasWanted"]; sint64 gas_used = 3 [json_name = "GasUsed"]; -} +} \ No newline at end of file diff --git a/tm2/pkg/std/memfile.go b/tm2/pkg/std/memfile.go index 5655876c9bb..01bc18c1487 100644 --- a/tm2/pkg/std/memfile.go +++ b/tm2/pkg/std/memfile.go @@ -8,8 +8,8 @@ import ( ) type MemFile struct { - Name string - Body string + Name string `json:"name" yaml:"name"` + Body string `json:"body" yaml:"body"` } // MemPackage represents the information and files of a package which will be @@ -19,9 +19,9 @@ type MemFile struct { // NOTE: in the future, a MemPackage may represent // updates/additional-files for an existing package. type MemPackage struct { - Name string // package name as declared by `package` - Path string // import path - Files []*MemFile + Name string `json:"name" yaml:"name"` // package name as declared by `package` + Path string `json:"path" yaml:"path"` // import path + Files []*MemFile `json:"files" yaml:"files"` } func (mempkg *MemPackage) GetFile(name string) *MemFile { @@ -37,6 +37,8 @@ func (mempkg *MemPackage) IsEmpty() bool { return len(mempkg.Files) == 0 } +const pathLengthLimit = 256 + var ( rePkgName = regexp.MustCompile(`^[a-z][a-z0-9_]*$`) rePkgOrRlmPath = regexp.MustCompile(`^gno\.land\/(?:p|r)(?:\/_?[a-z]+[a-z0-9_]*)+$`) @@ -52,9 +54,14 @@ func (mempkg *MemPackage) Validate() error { return fmt.Errorf("no files found within package %q", mempkg.Name) } + if len(mempkg.Path) > pathLengthLimit { + return fmt.Errorf("path length %d exceeds limit %d", len(mempkg.Path), pathLengthLimit) + } + if !rePkgName.MatchString(mempkg.Name) { return fmt.Errorf("invalid package name %q, failed to match %q", mempkg.Name, rePkgName) } + if !rePkgOrRlmPath.MatchString(mempkg.Path) { return fmt.Errorf("invalid package/realm path %q, failed to match %q", mempkg.Path, rePkgOrRlmPath) } @@ -83,18 +90,23 @@ func (mempkg *MemPackage) Validate() error { return nil } +const licenseName = "LICENSE" + // Splits a path into the dirpath and filename. func SplitFilepath(filepath string) (dirpath string, filename string) { parts := strings.Split(filepath, "/") if len(parts) == 1 { return parts[0], "" } - last := parts[len(parts)-1] - if strings.Contains(last, ".") { + + switch last := parts[len(parts)-1]; { + case strings.Contains(last, "."): return strings.Join(parts[:len(parts)-1], "/"), last - } else if last == "" { + case last == "": return strings.Join(parts[:len(parts)-1], "/"), "" - } else { - return strings.Join(parts, "/"), "" + case last == licenseName: + return strings.Join(parts[:len(parts)-1], "/"), licenseName } + + return strings.Join(parts, "/"), "" } diff --git a/tm2/pkg/std/memfile_test.go b/tm2/pkg/std/memfile_test.go index 375e0d1a41a..3e1fb49e131 100644 --- a/tm2/pkg/std/memfile_test.go +++ b/tm2/pkg/std/memfile_test.go @@ -7,6 +7,7 @@ import ( ) func TestMemPackage_Validate(t *testing.T) { + t.Parallel() tt := []struct { name string mpkg *MemPackage @@ -28,7 +29,7 @@ func TestMemPackage_Validate(t *testing.T) { Path: "gno.land/r/demo/hey", Files: []*MemFile{{Name: "b.gno"}, {Name: "a.gno"}}, }, - `mempackage "gno.land/r/demo/hey" has unsorted files`, + "unsorted", }, { "Duplicate", @@ -37,166 +38,286 @@ func TestMemPackage_Validate(t *testing.T) { Path: "gno.land/r/demo/hey", Files: []*MemFile{{Name: "a.gno"}, {Name: "a.gno"}}, }, - `duplicate file name "a.gno"`, + "duplicate", + }, + { + "InvalidPathLength", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/long/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "path length", + }, + { + "valid p", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/path/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "", + }, + { + "valid r", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "", }, - } - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - err := tc.mpkg.Validate() - if tc.errContains == "" { - assert.NoError(t, err) - } else { - assert.ErrorContains(t, err, tc.errContains) - } - }) - } -} - -func TestRePkgOrRlmPath(t *testing.T) { - t.Parallel() - - testTable := []struct { - desc, in string - expected bool - }{ { - desc: "Valid p", - in: "gno.land/p/path/path", - expected: true, + "Leading underscore", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/_path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "", + }, + { + "Trailing underscore", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/path_", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "", }, { - desc: "Valid r", - in: "gno.land/r/path/path", - expected: true, + "Between underscore", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/p_ath", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "", }, { - desc: "Leading Underscore", - in: "gno.land/r/path/_path", - expected: true, + "Invalid underscore", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/_", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Trailing Underscore", - in: "gno.land/r/path/path_", - expected: true, + "Invalid underscore 2", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/_/_", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Underscore in Between", - in: "gno.land/r/path/p_ath", - expected: true, + "Invalid underscore 3", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/__/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Invalid With Underscore 1", - in: "gno.land/r/path/_", - expected: false, + "Invalid hyphen", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/path/pa-th", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Invalid With Underscore 2", - in: "gno.land/r/path/_/_", - expected: false, + "Invalid x", + &MemPackage{ + Name: "hey", + Path: "gno.land/x/path/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Invalid With Underscore 3", - in: "gno.land/r/path/__/path", - expected: false, + "Invalid missing path 1", + &MemPackage{ + Name: "hey", + Path: "gno.land/p", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Invalid With Hyphen", - in: "gno.land/r/path/pa-th", - expected: false, + "Invalid missing path 2", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Invalid x", - in: "gno.land/x/path/path", - expected: false, + "Invalid path", + &MemPackage{ + Name: "hey", + Path: "github.com/p/path/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Missing Path 1", - in: "gno.land/p", - expected: false, + "Special character", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/p@th/abc/def", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Missing Path 2", - in: "gno.land/p/", - expected: false, + "Special character 2", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/p&th/abc/def", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Invalid domain", - in: "github.com/p/path/path", - expected: false, + "Invalid number", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/1Path/abc/def", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Special Character 1", - in: "gno.land/p/p@th/abc/def", - expected: false, + "Invalid uppercase", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/PaTh/abc/def", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Special Character 2", - in: "gno.land/p/p&th/abc/def", - expected: false, + "Invalid empty path", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/path//def", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Special Character 3", - in: "gno.land/p/p&%$#h/abc/def", - expected: false, + "Invalid trailing slash", + &MemPackage{ + Name: "hey", + Path: "gno.land/p/path/abc/def/", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Leading Number", - in: "gno.land/p/1Path/abc/def", - expected: false, + "valid long path", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/very/very/very/long/path", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "", }, { - desc: "Uppercase Letters", - in: "gno.land/p/PaTh/abc/def", - expected: false, + "Invalid long path with special character", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/very/very/very/long/p@th", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Empty Path Part", - in: "gno.land/p/path//def", - expected: false, + "Invalid long path with trailing slash", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/very/very/very/long/path/", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, { - desc: "Trailing Slash", - in: "gno.land/p/path/abc/def/", - expected: false, + "Invalid long path with empty", + &MemPackage{ + Name: "hey", + Path: "gno.land/r/very/very/very//long/path/", + Files: []*MemFile{{Name: "a.gno"}}, + }, + "invalid package/realm path", }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + err := tc.mpkg.Validate() + if tc.errContains != "" { + assert.ErrorContains(t, err, tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestSplitFilepath(t *testing.T) { + t.Parallel() + tests := []struct { + name string + filepath string + expDirPath string + expFilename string + }{ { - desc: "Extra Slash(s)", - in: "gno.land/p/path///abc/def", - expected: false, + name: "empty", }, { - desc: "Valid Long path", - in: "gno.land/r/very/very/very/long/path", - expected: true, + name: "one part", + filepath: "root", + expDirPath: "root", }, { - desc: "Long Path With Special Character 1", - in: "gno.land/r/very/very/very/long/p@th", - expected: false, + name: "file", + filepath: "gno.land/r/demo/avl/avl.gno", + expDirPath: "gno.land/r/demo/avl", + expFilename: "avl.gno", }, { - desc: "Long Path With Special Character 2", - in: "gno.land/r/very/very/v%ry/long/path", - expected: false, + name: "trailing slash", + filepath: "gno.land/r/demo/avl/", + expDirPath: "gno.land/r/demo/avl", }, { - desc: "Long Path With Trailing Slash", - in: "gno.land/r/very/very/very/long/path/", - expected: false, + name: "license", + filepath: "gno.land/r/demo/avl/LICENSE", + expDirPath: "gno.land/r/demo/avl", + expFilename: "LICENSE", }, { - desc: "Long Path With Empty Path Part", - in: "gno.land/r/very/very/very//long/path/", - expected: false, + name: "regular path", + filepath: "gno.land/p/demo/avl", + expDirPath: "gno.land/p/demo/avl", }, } - for _, tc := range testTable { - tc := tc - t.Run(tc.desc, func(t *testing.T) { + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { t.Parallel() - - assert.Equal(t, tc.expected, rePkgOrRlmPath.MatchString(tc.in)) + dirPath, filename := SplitFilepath(tt.filepath) + assert.Equal(t, tt.expDirPath, dirPath) + assert.Equal(t, tt.expFilename, filename) }) } } diff --git a/tm2/pkg/std/tx.go b/tm2/pkg/std/tx.go index 3068022ae03..ff7fdc54467 100644 --- a/tm2/pkg/std/tx.go +++ b/tm2/pkg/std/tx.go @@ -1,14 +1,22 @@ package std import ( + "bufio" + "context" "fmt" + "io" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/multisig" + "github.com/gnolang/gno/tm2/pkg/errors" ) -var maxGasWanted = int64((1 << 60) - 1) // something smaller than math.MaxInt64 +var ( + maxGasWanted = int64((1 << 60) - 1) // something smaller than math.MaxInt64 + + ErrTxsLoadingAborted = errors.New("transaction loading aborted") +) // Tx is a standard way to wrap a Msg with Fee and Signatures. // NOTE: the first signature is the fee payer (Signatures must not be nil). @@ -52,6 +60,34 @@ func (tx Tx) ValidateBasic() error { return nil } +// IsSponsorTx checks if the transaction is a valid sponsor transaction +func (tx Tx) IsSponsorTx() bool { + // At least two message in the transaction + if len(tx.Msgs) <= 1 { + return false + } + + // The first message type is "no_op" + if tx.Msgs[0].Type() != "no_op" { + return false + } + + // More than one signer + signers := tx.GetSigners() + if len(signers) <= 1 { + return false + } + + // The first signer is different from all other signers + for i := 1; i < len(signers); i++ { + if signers[0] == signers[i] { + return false + } + } + + return true +} + // CountSubKeys counts the total number of keys for a multi-sig public key. func CountSubKeys(pub crypto.PubKey) int { v, ok := pub.(multisig.PubKeyMultisigThreshold) @@ -136,3 +172,36 @@ func (fee Fee) Bytes() []byte { } return bz } + +func ParseTxs(ctx context.Context, reader io.Reader) ([]Tx, error) { + var txs []Tx + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil, ErrTxsLoadingAborted + default: + // Parse the amino JSON + var tx Tx + if err := amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { + return nil, fmt.Errorf( + "unable to unmarshal amino JSON, %w", + err, + ) + } + + txs = append(txs, tx) + } + } + + // Check for scanning errors + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf( + "error encountered while reading file, %w", + err, + ) + } + + return txs, nil +} diff --git a/tm2/pkg/std/tx_test.go b/tm2/pkg/std/tx_test.go new file mode 100644 index 00000000000..e348eb93496 --- /dev/null +++ b/tm2/pkg/std/tx_test.go @@ -0,0 +1,280 @@ +package std + +import ( + "testing" + + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/crypto/multisig" + "github.com/stretchr/testify/require" +) + +// Mock implementations of Msg interfaces +type mockMsg struct { + caller crypto.Address + msgType string +} + +func (m mockMsg) ValidateBasic() error { + return nil +} + +func (m mockMsg) GetSignBytes() []byte { + return nil +} + +func (m mockMsg) GetSigners() []crypto.Address { + return []crypto.Address{m.caller} +} + +func (m mockMsg) Route() string { + return "" +} + +func (m mockMsg) Type() string { + return m.msgType +} + +func TestNewTx(t *testing.T) { + addr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + msgs := []Msg{ + mockMsg{ + caller: addr, + }, + } + + fee := NewFee(1000, Coin{Denom: "atom", Amount: 10}) + + sigs := []Signature{ + { + Signature: []byte{0x00}, + }, + } + + memo := "test memo" + + tx := NewTx(msgs, fee, sigs, memo) + require.Equal(t, msgs, tx.GetMsgs()) + require.Equal(t, fee, tx.Fee) + require.Equal(t, sigs, tx.GetSignatures()) + require.Equal(t, memo, tx.GetMemo()) +} + +func TestValidateBasic(t *testing.T) { + addr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + msgs := []Msg{ + mockMsg{ + caller: addr, + }, + } + + fee := NewFee(maxGasWanted, Coin{Denom: "atom", Amount: 10}) + sigs := []Signature{ + { + Signature: []byte{0x00}, + }, + } + + tx := NewTx(msgs, fee, sigs, "test memo") + + // Valid case + require.NoError(t, tx.ValidateBasic()) + + // Invalid gas case + invalidFee := NewFee(maxGasWanted+1, Coin{Denom: "atom", Amount: 10}) + txInvalidGas := NewTx(msgs, invalidFee, sigs, "test memo") + require.Error(t, txInvalidGas.ValidateBasic(), "expected gas overflow error") + + // Invalid fee case + invalidFeeAmount := NewFee(1000, Coin{Denom: "atom", Amount: -10}) + txInvalidFee := NewTx(msgs, invalidFeeAmount, sigs, "test memo") + require.Error(t, txInvalidFee.ValidateBasic(), "expected insufficient fee error") + + // No signatures case + txNoSigs := NewTx(msgs, fee, []Signature{}, "test memo") + require.Error(t, txNoSigs.ValidateBasic(), "expected no signatures error") + + // Wrong number of signers case + wrongSigs := []Signature{ + { + Signature: []byte{0x00}, + }, + { + Signature: []byte{0x01}, + }, + } + txWrongSigs := NewTx(msgs, fee, wrongSigs, "test memo") + require.Error(t, txWrongSigs.ValidateBasic(), "expected wrong number of signers error") +} + +func TestCountSubKeys(t *testing.T) { + // Single key case + pubKey := ed25519.GenPrivKey().PubKey() + require.Equal(t, 1, CountSubKeys(pubKey)) + + // Multi-sig case + // Assuming multisig.PubKeyMultisigThreshold is correctly implemented for testing purposes + pubKeys := []crypto.PubKey{ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey()} + multisigPubKey := multisig.NewPubKeyMultisigThreshold(2, pubKeys) + require.Equal(t, len(pubKeys), CountSubKeys(multisigPubKey)) +} + +func TestGetSigners(t *testing.T) { + // Single signer case + addr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + msgs := []Msg{ + mockMsg{ + caller: addr, + msgType: "call", + }, + } + tx := NewTx(msgs, Fee{}, []Signature{}, "") + require.Equal(t, []crypto.Address{addr}, tx.GetSigners()) + + // Duplicate signers case + msgs = []Msg{ + mockMsg{ + caller: addr, + msgType: "send", + }, + mockMsg{ + caller: addr, + msgType: "send", + }, + } + + tx = NewTx(msgs, Fee{}, []Signature{}, "") + require.Equal(t, []crypto.Address{addr}, tx.GetSigners()) + + // Multiple unique signers case + addr2, _ := crypto.AddressFromBech32("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + msgs = []Msg{ + mockMsg{ + caller: addr, + msgType: "call", + }, + mockMsg{ + caller: addr2, + msgType: "run", + }, + } + tx = NewTx(msgs, Fee{}, []Signature{}, "") + require.Equal(t, []crypto.Address{addr, addr2}, tx.GetSigners()) + + // "no_op" message case + msgs = []Msg{ + mockMsg{ + caller: addr, + msgType: "no_op", + }, + mockMsg{ + caller: addr2, + msgType: "call", + }, + } + tx = NewTx(msgs, Fee{}, []Signature{}, "") + require.Equal(t, []crypto.Address{addr, addr2}, tx.GetSigners()) +} + +func TestGetSignBytes(t *testing.T) { + msgs := []Msg{} + fee := NewFee(1000, Coin{Denom: "atom", Amount: 10}) + sigs := []Signature{} + tx := NewTx(msgs, fee, sigs, "test memo") + chainID := "test-chain" + accountNumber := uint64(1) + sequence := uint64(1) + + signBytes, err := tx.GetSignBytes(chainID, accountNumber, sequence) + require.NoError(t, err) + require.NotEmpty(t, signBytes) +} + +func TestIsSponsorTx(t *testing.T) { + addr1, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + addr2, _ := crypto.AddressFromBech32("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + tests := []struct { + name string + msgs []Msg + expected bool + }{ + { + name: "no_op message with different signers", + msgs: []Msg{ + mockMsg{ + caller: addr1, + msgType: "no_op", + }, + mockMsg{ + caller: addr2, + msgType: "call", + }, + }, + expected: true, + }, + { + name: "only have no_op message", + msgs: []Msg{ + mockMsg{ + caller: addr1, + msgType: "no_op", + }, + }, + expected: false, + }, + { + name: "no_op message with same signers", + msgs: []Msg{ + mockMsg{ + caller: addr1, + msgType: "no_op", + }, + mockMsg{ + caller: addr1, + msgType: "call", + }, + }, + expected: false, + }, + { + name: "non no_op message", + msgs: []Msg{ + mockMsg{ + caller: addr1, + msgType: "call", + }, + mockMsg{ + caller: addr2, + msgType: "send", + }, + }, + expected: false, + }, + { + name: "no_op message with single signer", + msgs: []Msg{ + mockMsg{ + caller: addr1, + msgType: "no_op", + }, + }, + expected: false, + }, + { + name: "empty messages", + msgs: []Msg{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tx := Tx{ + Msgs: tt.msgs, + } + result := tx.IsSponsorTx() + require.Equal(t, tt.expected, result) + }) + } +} diff --git a/tm2/pkg/store/cache/store.go b/tm2/pkg/store/cache/store.go index 1c5c2e27783..6bc6ad3b71f 100644 --- a/tm2/pkg/store/cache/store.go +++ b/tm2/pkg/store/cache/store.go @@ -3,13 +3,17 @@ package cache import ( "bytes" "container/list" + "fmt" + "reflect" "sort" "sync" + "github.com/gnolang/gno/tm2/pkg/colors" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/store/utils" ) // If value is nil but deleted is false, it means the parent doesn't have the @@ -20,6 +24,12 @@ type cValue struct { dirty bool } +func (cv cValue) String() string { + return fmt.Sprintf("cValue{%s,%v,%v}", + colors.DefaultColoredBytes(cv.value), + cv.deleted, cv.dirty) +} + // cacheStore wraps an in-memory cache around an underlying types.Store. type cacheStore struct { mtx sync.Mutex @@ -32,12 +42,13 @@ type cacheStore struct { var _ types.Store = (*cacheStore)(nil) func New(parent types.Store) *cacheStore { - return &cacheStore{ + cs := &cacheStore{ cache: make(map[string]*cValue), unsortedCache: make(map[string]struct{}), sortedCache: list.New(), parent: parent, } + return cs } // Implements types.Store. @@ -112,6 +123,17 @@ func (store *cacheStore) Write() { } // Clear the cache + store.clear() +} + +func (store *cacheStore) Flush() { + store.Write() + if fs, ok := store.parent.(types.Flusher); ok { + fs.Flush() + } +} + +func (store *cacheStore) clear() { store.cache = make(map[string]*cValue) store.unsortedCache = make(map[string]struct{}) store.sortedCache = list.New() @@ -209,3 +231,23 @@ func (store *cacheStore) setCacheValue(key, value []byte, deleted bool, dirty bo store.unsortedCache[string(key)] = struct{}{} } } + +func (store *cacheStore) Print() { + fmt.Println(colors.Cyan("cacheStore.Print"), fmt.Sprintf("%p", store)) + for key, value := range store.cache { + fmt.Println( + colors.DefaultColoredBytesN([]byte(key), 50), + colors.DefaultColoredBytesN(value.value, 100), + "deleted", value.deleted, + "dirty", value.dirty, + ) + } + fmt.Println(colors.Cyan("cacheStore.Print"), fmt.Sprintf("%p", store), + "print parent", fmt.Sprintf("%p", store.parent), reflect.TypeOf(store.parent)) + if ps, ok := store.parent.(types.Printer); ok { + ps.Print() + } else { + utils.Print(store.parent) + } + fmt.Println(colors.Cyan("cacheStore.Print END"), fmt.Sprintf("%p", store)) +} diff --git a/tm2/pkg/store/gas/store.go b/tm2/pkg/store/gas/store.go index 4ffe46dc275..db5ea7a79b0 100644 --- a/tm2/pkg/store/gas/store.go +++ b/tm2/pkg/store/gas/store.go @@ -2,6 +2,7 @@ package gas import ( "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/store/utils" "github.com/gnolang/overflow" ) @@ -100,6 +101,22 @@ func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator { return gi } +func (gs *Store) Print() { + if ps, ok := gs.parent.(types.Printer); ok { + ps.Print() + } else { + utils.Print(gs.parent) + } +} + +func (gs *Store) Flush() { + if cts, ok := gs.parent.(types.Flusher); ok { + cts.Flush() + } else { + panic("underlying store does not implement Flush()") + } +} + type gasIterator struct { gasMeter types.GasMeter gasConfig types.GasConfig diff --git a/tm2/pkg/store/store.go b/tm2/pkg/store/store.go index 2950937f951..ba939fa1793 100644 --- a/tm2/pkg/store/store.go +++ b/tm2/pkg/store/store.go @@ -1,10 +1,7 @@ package store import ( - "fmt" - dbm "github.com/gnolang/gno/tm2/pkg/db" - "github.com/gnolang/gno/tm2/pkg/strings" "github.com/gnolang/gno/tm2/pkg/store/rootmulti" "github.com/gnolang/gno/tm2/pkg/store/types" @@ -27,26 +24,3 @@ func NewPruningOptionsFromString(strategy string) (opt PruningOptions) { } return } - -// TODO move to another file. -func Print(store Store) { - fmt.Println("//----------------------------------------") - fmt.Println("// store:", store) - itr := store.Iterator(nil, nil) - defer itr.Close() - for ; itr.Valid(); itr.Next() { - key, value := itr.Key(), itr.Value() - var keystr, valuestr string - if strings.IsASCIIText(string(key)) { - keystr = string(key) - } else { - keystr = fmt.Sprintf("0x%X", key) - } - if strings.IsASCIIText(string(value)) { - valuestr = string(value) - } else { - valuestr = fmt.Sprintf("0x%X", value) - } - fmt.Printf("%s: %s\n", keystr, valuestr) - } -} diff --git a/tm2/pkg/store/types/store.go b/tm2/pkg/store/types/store.go index 68ee110586c..a0e3925a265 100644 --- a/tm2/pkg/store/types/store.go +++ b/tm2/pkg/store/types/store.go @@ -40,7 +40,8 @@ type Store interface { // Returns a cache-wrapped store. CacheWrap() Store - // If cache-wrapped store, flushes to underlying store. + // If cache-wrapped store, writes to underlying store. + // Does not writes through layers of cache. Write() } @@ -55,6 +56,23 @@ type Queryable interface { Query(abci.RequestQuery) abci.ResponseQuery } +// Useful for debugging. +type Printer interface { + Print() +} + +// Write through all caches. +// You probably don't want this, rather write your program to Write() where +// appropriate. Not included in the main Store interface to discourage usage. +type Flusher interface { + Flush() +} + +// Write throgh +type Writer interface { + Write() +} + // ---------------------------------------- // MultiStore diff --git a/tm2/pkg/store/utils/print.go b/tm2/pkg/store/utils/print.go new file mode 100644 index 00000000000..d47f4188db7 --- /dev/null +++ b/tm2/pkg/store/utils/print.go @@ -0,0 +1,38 @@ +package utils + +import ( + "fmt" + "reflect" + + "github.com/gnolang/gno/tm2/pkg/colors" + "github.com/gnolang/gno/tm2/pkg/store/types" +) + +// TODO move to another file. +func Print(store types.Store) { + fmt.Println(colors.Blue("//----------------------------------------")) + if store == nil { + fmt.Println("") + } else if ps, ok := store.(types.Printer); ok { + ps.Print() + } else { + fmt.Println(colors.Blue(fmt.Sprintf("// store:%p %v", store, reflect.TypeOf(store)))) + itr := store.Iterator(nil, nil) + defer itr.Close() + for ; itr.Valid(); itr.Next() { + key, value := itr.Key(), itr.Value() + var keystr, valuestr string + keystr = colors.DefaultColoredBytesN(key, 100) + valuestr = fmt.Sprintf("(%d)", len(value)) + /* + if true || strings.IsASCIIText(string(value)) { + valuestr = string(value) + } else { + valuestr = fmt.Sprintf("0x%X", value) + } + */ + fmt.Printf("%s: %s\n", keystr, valuestr) + } + } + fmt.Println(colors.Blue("//------------------------------------ end")) +} diff --git a/tm2/pkg/strings/string.go b/tm2/pkg/strings/string.go index 46675923319..86ed70373a3 100644 --- a/tm2/pkg/strings/string.go +++ b/tm2/pkg/strings/string.go @@ -75,3 +75,12 @@ func StringSliceEqual(a, b []string) bool { } return true } + +// TrimN naively appens "..." to fit within n bytes. +func TrimN(s string, n int) string { + if len(s) <= n { + return s + } else { + return s[:n-3] + "..." + } +} diff --git a/tm2/pkg/telemetry/README.md b/tm2/pkg/telemetry/README.md index cadaecc89ab..783b00de789 100644 --- a/tm2/pkg/telemetry/README.md +++ b/tm2/pkg/telemetry/README.md @@ -1,6 +1,7 @@ # Telemetry -The purpose of this package is to provide a way to easily integrate OpenTelemetry Protocol (OTLP) metrics collection into a Tendermint 2 node. +The purpose of this package is to provide a way to easily integrate OpenTelemetry Protocol (OTLP) metrics collection +into a Tendermint 2 node. ## Configure Telemetry @@ -8,10 +9,16 @@ Telemetry can be regularly configured within the TM2 node through the `[telemetry]` section. It is disabled by default. ## OTEL configuration -There are many ways configure the OTEL pipeline for exporting metrics. Here is an example of how a local OTEL collector can be configured to send metrics to Grafana Cloud. This is an optional step and can be highly customized. + +There are many ways configure the OTEL pipeline for exporting metrics. Here is an example of how a local OTEL collector +can be configured to send metrics to Grafana Cloud. This is an optional step and can be highly customized. ### OTEL collector -The latest collector releases can be found [here](https://github.com/open-telemetry/opentelemetry-collector-releases/releases). This is an example of the config that can be used to receive metrics from gno.land and publish them to Grafana Cloud. + +The latest collector releases can be +found [here](https://github.com/open-telemetry/opentelemetry-collector-releases/releases). This is an example of the +config that can be used to receive metrics from gno.land and publish them to Grafana Cloud. + ```yaml receivers: otlp: @@ -29,13 +36,15 @@ exporters: service: pipelines: metrics: - receivers: [otlp] - processors: [batch] - exporters: [otlphttp] + receivers: [ otlp ] + processors: [ batch ] + exporters: [ otlphttp ] ``` -Collector exporter environment variables, including those for authentication, can be found [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/). +Collector exporter environment variables, including those for authentication, can be +found [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/). ## Resources + - https://opentelemetry.io/docs/collector/ - https://grafana.com/docs/grafana-cloud/monitor-applications/application-observability/setup/collector/ diff --git a/tm2/pkg/telemetry/config/config.go b/tm2/pkg/telemetry/config/config.go index 96c31b48cfd..a9aa24d7848 100644 --- a/tm2/pkg/telemetry/config/config.go +++ b/tm2/pkg/telemetry/config/config.go @@ -1,27 +1,37 @@ -// Package config contains the configuration types and helpers for the telemetry -// package. package config -// Config is the configuration struct for the tm2 telemetry package. +import ( + "errors" +) + +var errEndpointNotSet = errors.New("telemetry exporter endpoint not set") + +// Config is the configuration struct for the tm2 telemetry package type Config struct { - MetricsEnabled bool `toml:"enabled"` - MeterName string `toml:"meter_name"` - ServiceName string `toml:"service_name"` - ExporterEndpoint string `toml:"exporter_endpoint" comment:"the endpoint to export metrics to, like a local OpenTelemetry collector"` + MetricsEnabled bool `json:"enabled" toml:"enabled"` + MeterName string `json:"meter_name" toml:"meter_name"` + ServiceName string `json:"service_name" toml:"service_name"` + ServiceInstanceID string `json:"service_instance_id" toml:"service_instance_id" comment:"the ID helps to distinguish instances of the same service that exist at the same time (e.g. instances of a horizontally scaled service)"` + ExporterEndpoint string `json:"exporter_endpoint" toml:"exporter_endpoint" comment:"the endpoint to export metrics to, like a local OpenTelemetry collector"` } -// DefaultTelemetryConfig is the default configuration used for the node. +// DefaultTelemetryConfig is the default configuration used for the node func DefaultTelemetryConfig() *Config { return &Config{ - MetricsEnabled: false, - MeterName: "gno.land", - ServiceName: "gno.land", - ExporterEndpoint: "", + MetricsEnabled: false, + MeterName: "gno.land", + ServiceName: "gno.land", + ServiceInstanceID: "gno-node-1", + ExporterEndpoint: "", } } -// TestTelemetryConfig is the test configuration. Currently it is an alias for -// [DefaultTelemetryConfig]. -func TestTelemetryConfig() *Config { - return DefaultTelemetryConfig() +// ValidateBasic performs basic telemetry config validation and +// returns an error if any check fails +func (cfg *Config) ValidateBasic() error { + if cfg.ExporterEndpoint == "" { + return errEndpointNotSet + } + + return nil } diff --git a/tm2/pkg/telemetry/config/config_test.go b/tm2/pkg/telemetry/config/config_test.go new file mode 100644 index 00000000000..c61258b886b --- /dev/null +++ b/tm2/pkg/telemetry/config/config_test.go @@ -0,0 +1,29 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_ValidateBasic(t *testing.T) { + t.Parallel() + + t.Run("exporter endpoint not set", func(t *testing.T) { + t.Parallel() + + c := DefaultTelemetryConfig() + c.ExporterEndpoint = "" // empty + + assert.ErrorIs(t, c.ValidateBasic(), errEndpointNotSet) + }) + + t.Run("valid configuration", func(t *testing.T) { + t.Parallel() + + c := DefaultTelemetryConfig() + c.ExporterEndpoint = "0.0.0.0:8080" + + assert.NoError(t, c.ValidateBasic()) + }) +} diff --git a/tm2/pkg/telemetry/exporter/error.go b/tm2/pkg/telemetry/exporter/error.go deleted file mode 100644 index 5df14f0dba3..00000000000 --- a/tm2/pkg/telemetry/exporter/error.go +++ /dev/null @@ -1,5 +0,0 @@ -package exporter - -import "errors" - -var ErrEndpointNotSet = errors.New("telemetry exporter endpoint not set") diff --git a/tm2/pkg/telemetry/init.go b/tm2/pkg/telemetry/init.go index 8d31c6268f8..d0b7a246e8a 100644 --- a/tm2/pkg/telemetry/init.go +++ b/tm2/pkg/telemetry/init.go @@ -4,6 +4,7 @@ package telemetry // https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go import ( + "fmt" "sync/atomic" "github.com/gnolang/gno/tm2/pkg/telemetry/config" @@ -11,26 +12,34 @@ import ( ) var ( - globalConfig config.Config - globalConfigSet atomic.Bool + globalConfig config.Config + telemetryInitialized atomic.Bool ) -// MetricsEnabled returns true if metrics have been initialized. +// MetricsEnabled returns true if metrics have been initialized func MetricsEnabled() bool { return globalConfig.MetricsEnabled } -// Init sets the configuration for telemetry to c, and if telemetry is enabled, -// starts tracking. -// Init may only be called once. Multiple calls to Init will panic. +// Init initializes the global telemetry func Init(c config.Config) error { - if !globalConfigSet.CompareAndSwap(false, true) { - panic("telemetry configuration has already been set and initialised") + // Check if the metrics are enabled at all + if !c.MetricsEnabled { + return nil } - globalConfig = c - // Initialize metrics to be collected. - if c.MetricsEnabled { - return metrics.Init(c) + + // Validate the configuration + if err := c.ValidateBasic(); err != nil { + return fmt.Errorf("unable to validate config, %w", err) + } + + // Check if it's been enabled already + if !telemetryInitialized.CompareAndSwap(false, true) { + return nil } - return nil + + // Update the global configuration + globalConfig = c + + return metrics.Init(c) } diff --git a/tm2/pkg/telemetry/metrics/init.go b/tm2/pkg/telemetry/metrics/init.go deleted file mode 100644 index ac67a8d71ce..00000000000 --- a/tm2/pkg/telemetry/metrics/init.go +++ /dev/null @@ -1,69 +0,0 @@ -package metrics - -import ( - "context" - - "github.com/gnolang/gno/tm2/pkg/telemetry/config" - "github.com/gnolang/gno/tm2/pkg/telemetry/exporter" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" - "go.opentelemetry.io/otel/metric" - sdkMetric "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" -) - -var ( - // Metrics. - BroadcastTxTimer metric.Int64Histogram - BuildBlockTimer metric.Int64Histogram -) - -func Init(config config.Config) error { - if config.ExporterEndpoint == "" { - return exporter.ErrEndpointNotSet - } - - // Use oltp metric exporter. - exporter, err := otlpmetricgrpc.New( - context.Background(), - otlpmetricgrpc.WithEndpoint(config.ExporterEndpoint), - otlpmetricgrpc.WithInsecure(), // TODO: enable security - ) - if err != nil { - return err - } - - provider := sdkMetric.NewMeterProvider( - // Default period is 1m. - sdkMetric.WithReader(sdkMetric.NewPeriodicReader(exporter)), - sdkMetric.WithResource( - resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String(config.ServiceName), - semconv.ServiceVersionKey.String("1.0.0"), - semconv.ServiceInstanceIDKey.String("gno-node-1"), - ), - ), - ) - otel.SetMeterProvider(provider) - meter := provider.Meter(config.MeterName) - - if BroadcastTxTimer, err = meter.Int64Histogram( - "broadcast_tx_hist", - metric.WithDescription("broadcast tx duration"), - metric.WithUnit("ms"), - ); err != nil { - return err - } - - if BuildBlockTimer, err = meter.Int64Histogram( - "build_block_hist", - metric.WithDescription("block build duration"), - metric.WithUnit("ms"), - ); err != nil { - return err - } - - return nil -} diff --git a/tm2/pkg/telemetry/metrics/metrics.go b/tm2/pkg/telemetry/metrics/metrics.go new file mode 100644 index 00000000000..2b04769fe0c --- /dev/null +++ b/tm2/pkg/telemetry/metrics/metrics.go @@ -0,0 +1,309 @@ +package metrics + +import ( + "context" + "fmt" + "net/url" + + "github.com/gnolang/gno/tm2/pkg/telemetry/config" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/metric" + sdkMetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +const ( + broadcastTxTimerKey = "broadcast_tx_hist" + buildBlockTimerKey = "build_block_hist" + + inboundPeersKey = "inbound_peers_hist" + outboundPeersKey = "outbound_peers_hist" + dialingPeersKey = "dialing_peers_hist" + + numMempoolTxsKey = "num_mempool_txs_hist" + numCachedTxsKey = "num_cached_txs_hist" + + vmQueryCallsKey = "vm_query_calls_counter" + vmQueryErrorsKey = "vm_query_errors_counter" + vmGasUsedKey = "vm_gas_used_hist" + vmCPUCyclesKey = "vm_cpu_cycles_hist" + vmExecMsgKey = "vm_exec_msg_hist" + + validatorCountKey = "validator_count_hist" + validatorVotingPowerKey = "validator_vp_hist" + blockIntervalKey = "block_interval_hist" + blockTxsKey = "block_txs_hist" + blockSizeKey = "block_size_hist" + + httpRequestTimeKey = "http_request_time_hist" + wsRequestTimeKey = "ws_request_time_hist" +) + +var ( + // Misc // + + // BroadcastTxTimer measures the transaction broadcast duration + BroadcastTxTimer metric.Int64Histogram + + // Networking // + + // InboundPeers measures the active number of inbound peers + InboundPeers metric.Int64Histogram + + // OutboundPeers measures the active number of outbound peers + OutboundPeers metric.Int64Histogram + + // DialingPeers measures the active number of peers in the dialing state + DialingPeers metric.Int64Histogram + + // Mempool // + + // NumMempoolTxs measures the number of transaction inside the mempool + NumMempoolTxs metric.Int64Histogram + + // NumCachedTxs measures the number of transaction inside the mempool cache + NumCachedTxs metric.Int64Histogram + + // Runtime // + + // VMQueryCalls measures the frequency of VM query calls + VMQueryCalls metric.Int64Counter + + // VMQueryErrors measures the frequency of VM query errors + VMQueryErrors metric.Int64Counter + + // VMGasUsed measures the VM gas usage + VMGasUsed metric.Int64Histogram + + // VMCPUCycles measures the VM CPU cycles + VMCPUCycles metric.Int64Histogram + + // VMExecMsgFrequency measures the frequency of VM operations + VMExecMsgFrequency metric.Int64Counter + + // Consensus // + + // BuildBlockTimer measures the block build duration + BuildBlockTimer metric.Int64Histogram + + // ValidatorsCount measures the size of the active validator set + ValidatorsCount metric.Int64Histogram + + // ValidatorsVotingPower measures the total voting power of the active validator set + ValidatorsVotingPower metric.Int64Histogram + + // BlockInterval measures the interval between 2 subsequent blocks + BlockInterval metric.Int64Histogram + + // BlockTxs measures the number of transactions within the latest block + BlockTxs metric.Int64Histogram + + // BlockSizeBytes measures the size of the latest block in bytes + BlockSizeBytes metric.Int64Histogram + + // RPC // + + // HTTPRequestTime measures the HTTP request response time + HTTPRequestTime metric.Int64Histogram + + // WSRequestTime measures the WS request response time + WSRequestTime metric.Int64Histogram +) + +func Init(config config.Config) error { + var ( + ctx = context.Background() + exp sdkMetric.Exporter + ) + + u, err := url.Parse(config.ExporterEndpoint) + if err != nil { + return fmt.Errorf("error parsing exporter endpoint: %s, %w", config.ExporterEndpoint, err) + } + + // Use oltp metric exporter with http/https or grpc + switch u.Scheme { + case "http", "https": + exp, err = otlpmetrichttp.New( + ctx, + otlpmetrichttp.WithEndpointURL(config.ExporterEndpoint), + ) + if err != nil { + return fmt.Errorf("unable to create http metrics exporter, %w", err) + } + default: + exp, err = otlpmetricgrpc.New( + ctx, + otlpmetricgrpc.WithEndpoint(config.ExporterEndpoint), + otlpmetricgrpc.WithInsecure(), + ) + if err != nil { + return fmt.Errorf("unable to create grpc metrics exporter, %w", err) + } + } + + provider := sdkMetric.NewMeterProvider( + // Default period is 1m + sdkMetric.WithReader(sdkMetric.NewPeriodicReader(exp)), + sdkMetric.WithResource( + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(config.ServiceName), + semconv.ServiceVersionKey.String("1.0.0"), + semconv.ServiceInstanceIDKey.String(config.ServiceInstanceID), + ), + ), + ) + otel.SetMeterProvider(provider) + meter := provider.Meter(config.MeterName) + + if BroadcastTxTimer, err = meter.Int64Histogram( + broadcastTxTimerKey, + metric.WithDescription("broadcast tx duration"), + metric.WithUnit("ms"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if BuildBlockTimer, err = meter.Int64Histogram( + buildBlockTimerKey, + metric.WithDescription("block build duration"), + metric.WithUnit("ms"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + // Networking // + if InboundPeers, err = meter.Int64Histogram( + inboundPeersKey, + metric.WithDescription("inbound peer count"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if OutboundPeers, err = meter.Int64Histogram( + outboundPeersKey, + metric.WithDescription("outbound peer count"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if DialingPeers, err = meter.Int64Histogram( + dialingPeersKey, + metric.WithDescription("dialing peer count"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + // Mempool // + if NumMempoolTxs, err = meter.Int64Histogram( + numMempoolTxsKey, + metric.WithDescription("valid mempool transaction count"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if NumCachedTxs, err = meter.Int64Histogram( + numCachedTxsKey, + metric.WithDescription("cached mempool transaction count"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + // Runtime // + if VMQueryCalls, err = meter.Int64Counter( + vmQueryCallsKey, + metric.WithDescription("vm query call frequency"), + ); err != nil { + return fmt.Errorf("unable to create counter, %w", err) + } + + if VMQueryErrors, err = meter.Int64Counter( + vmQueryErrorsKey, + metric.WithDescription("vm query errors call frequency"), + ); err != nil { + return fmt.Errorf("unable to create counter, %w", err) + } + + if VMGasUsed, err = meter.Int64Histogram( + vmGasUsedKey, + metric.WithDescription("VM gas used"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if VMCPUCycles, err = meter.Int64Histogram( + vmCPUCyclesKey, + metric.WithDescription("VM CPU cycles"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if VMExecMsgFrequency, err = meter.Int64Counter( + vmExecMsgKey, + metric.WithDescription("vm msg operation call frequency"), + ); err != nil { + return fmt.Errorf("unable to create counter, %w", err) + } + + // Consensus // + if ValidatorsCount, err = meter.Int64Histogram( + validatorCountKey, + metric.WithDescription("size of the active validator set"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if ValidatorsVotingPower, err = meter.Int64Histogram( + validatorVotingPowerKey, + metric.WithDescription("total voting power of the active validator set"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if BlockInterval, err = meter.Int64Histogram( + blockIntervalKey, + metric.WithDescription("interval between 2 subsequent blocks"), + metric.WithUnit("s"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if BlockTxs, err = meter.Int64Histogram( + blockTxsKey, + metric.WithDescription("number of transactions within the latest block"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if BlockSizeBytes, err = meter.Int64Histogram( + blockSizeKey, + metric.WithDescription("size of the latest block in bytes"), + metric.WithUnit("B"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + // RPC // + + if HTTPRequestTime, err = meter.Int64Histogram( + httpRequestTimeKey, + metric.WithDescription("http request response time"), + metric.WithUnit("ms"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + if WSRequestTime, err = meter.Int64Histogram( + wsRequestTimeKey, + metric.WithDescription("ws request response time"), + metric.WithUnit("ms"), + ); err != nil { + return fmt.Errorf("unable to create histogram, %w", err) + } + + return nil +}