diff --git a/.gitattributes b/.gitattributes index c22d136ec50..adc4144ffa3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ *.gno linguist-language=Go *.pb.go linguist-generated merge=ours -diff go.sum linguist-generated text -gnovm/stdlibs/native.go linguist-generated -gnovm/tests/stdlibs/native.go linguist-generated +gnovm/stdlibs/generated.go linguist-generated +gnovm/tests/stdlibs/generated.go linguist-generated 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/goreleaser.yaml b/.github/goreleaser.yaml index ab98aa92555..1984493d36f 100644 --- a/.github/goreleaser.yaml +++ b/.github/goreleaser.yaml @@ -1,4 +1,10 @@ project_name: gno +version: 2 + +env: + - TAG_VERSION={{ if index .Env "TAG_VERSION" }}{{ .Env.TAG_VERSION }}{{ else }}latest{{ end }} + # supported in next versions -> https://github.com/goreleaser/goreleaser/issues/5059 + # - TAG_VERSION="{{ envOrDefault "TAG_VERSION" "latest" }}" before: hooks: @@ -65,6 +71,22 @@ builds: goarm: - 6 - 7 + - id: gnofaucet + dir: ./contribs/gnofaucet + binary: gnofaucet + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + gomod: proxy: true @@ -99,7 +121,7 @@ dockers: goarch: amd64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64" - - "ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-amd64" build_flag_templates: - "--target=gno" - "--platform=linux/amd64" @@ -119,7 +141,7 @@ dockers: goarch: arm64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8" - - "ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-arm64v8" build_flag_templates: - "--target=gno" - "--platform=linux/arm64/v8" @@ -140,7 +162,7 @@ dockers: goarm: 6 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}:latest-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv6" build_flag_templates: - "--target=gno" - "--platform=linux/arm/v6" @@ -161,7 +183,7 @@ dockers: goarm: 7 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}:latest-armv7" + - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv7" build_flag_templates: - "--target=gno" - "--platform=linux/arm/v7" @@ -183,7 +205,7 @@ dockers: goarch: amd64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-amd64" build_flag_templates: - "--target=gnoland" - "--platform=linux/amd64" @@ -204,7 +226,7 @@ dockers: goarch: arm64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-arm64v8" build_flag_templates: - "--target=gnoland" - "--platform=linux/arm64/v8" @@ -226,7 +248,7 @@ dockers: goarm: 6 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv6" build_flag_templates: - "--target=gnoland" - "--platform=linux/arm/v6" @@ -248,7 +270,7 @@ dockers: goarm: 7 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv7" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv7" build_flag_templates: - "--target=gnoland" - "--platform=linux/arm/v7" @@ -270,7 +292,7 @@ dockers: goarch: amd64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-amd64" build_flag_templates: - "--target=gnokey" - "--platform=linux/amd64" @@ -286,7 +308,7 @@ dockers: goarch: arm64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-arm64v8" build_flag_templates: - "--target=gnokey" - "--platform=linux/arm64/v8" @@ -303,7 +325,7 @@ dockers: goarm: 6 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv6" build_flag_templates: - "--target=gnokey" - "--platform=linux/arm/v6" @@ -320,7 +342,7 @@ dockers: goarm: 7 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv7" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv7" build_flag_templates: - "--target=gnokey" - "--platform=linux/arm/v7" @@ -338,7 +360,7 @@ dockers: goarch: amd64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-amd64" build_flag_templates: - "--target=gnoweb" - "--platform=linux/amd64" @@ -354,7 +376,7 @@ dockers: goarch: arm64 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-arm64v8" build_flag_templates: - "--target=gnoweb" - "--platform=linux/arm64/v8" @@ -371,7 +393,7 @@ dockers: goarm: 6 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv6" build_flag_templates: - "--target=gnoweb" - "--platform=linux/arm/v6" @@ -388,7 +410,7 @@ dockers: goarm: 7 image_templates: - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv7" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv7" build_flag_templates: - "--target=gnoweb" - "--platform=linux/arm/v7" @@ -399,6 +421,74 @@ dockers: ids: - gnoweb + # gnofaucet + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: amd64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-amd64" + build_flag_templates: + - "--target=gnofaucet" + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}/gnofaucet" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + ids: + - gnofaucet + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: arm64 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-arm64v8" + build_flag_templates: + - "--target=gnofaucet" + - "--platform=linux/arm64/v8" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}/gnofaucet" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + ids: + - gnofaucet + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: arm + goarm: 6 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv6" + build_flag_templates: + - "--target=gnofaucet" + - "--platform=linux/arm/v6" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}/gnofaucet" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + ids: + - gnofaucet + - use: buildx + dockerfile: Dockerfile.release + goos: linux + goarch: arm + goarm: 7 + image_templates: + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv7" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv7" + build_flag_templates: + - "--target=gnofaucet" + - "--platform=linux/arm/v7" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}/gnofaucet" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + ids: + - gnofaucet + docker_manifests: # https://goreleaser.com/customization/docker_manifest/ @@ -409,12 +499,12 @@ docker_manifests: - 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 + - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }} 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 + - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv7 # gnoland - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }} @@ -423,12 +513,12 @@ docker_manifests: - 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 + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }} 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 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv7 # gnokey - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }} @@ -437,13 +527,13 @@ docker_manifests: - 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 + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }} 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 - + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv7 + # gnoweb - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }} image_templates: @@ -451,12 +541,26 @@ docker_manifests: - 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 + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }} 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 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv7 + + # gnofaucet + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }} + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv7 + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }} + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-arm64v8 + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv6 + - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv7 docker_signs: - cmd: cosign @@ -484,6 +588,8 @@ sboms: artifacts: source release: + disable: '{{ if eq .Env.TAG_VERSION "master" }}true{{ else }}false{{ end }}' + skip_upload: '{{ if eq .Env.TAG_VERSION "master" }}true{{ else }}false{{ end }}' draft: true replace_existing_draft: true prerelease: auto @@ -493,4 +599,11 @@ release: You can find all docker images at: - https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }} \ No newline at end of file + https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }} + +# Only valid for nightly build +nightly: + tag_name: nightly + publish_release: true + keep_single_release: true + name_template: "{{ incpatch .Version }}-{{ .ShortCommit }}-{{ .Env.TAG_VERSION }}" 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 c8251a05490..66aced0d89c 100644 --- a/.github/workflows/autocounterd.yml +++ b/.github/workflows/autocounterd.yml @@ -23,7 +23,7 @@ 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 }} @@ -39,7 +39,7 @@ jobs: 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/contribs.yml b/.github/workflows/contribs.yml index 784dc9b71e5..3739339f7be 100644 --- a/.github/workflows/contribs.yml +++ b/.github/workflows/contribs.yml @@ -5,16 +5,7 @@ on: branches: - master workflow_dispatch: - pull_request: - paths: - - "contribs/**" - - ".github/**" - # Contribs directly depend on gno, so we need to test it whenever changes - # are made to one of those - - "go.*" # check on go.mod/sum update - - "gno.land/**" - - "tm2/**.go" - - "gnovm/**.go" + pull_request: jobs: setup: @@ -24,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: 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 index 18d75e46a81..262b341276c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d11710344b1..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" ] diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 9f1c11eb4ea..9de8d536b29 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -28,10 +28,10 @@ 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 + uses: coursier/setup-action@v1.3.6 with: jvm: temurin:1.17 diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 202933f8462..9451d6da3a1 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -6,12 +6,6 @@ on: - master workflow_dispatch: pull_request: - paths: - - "gno.land/**" - - "tm2/**.go" - - "gnovm/**.go" - - "go.*" # check on go.mod/sum update - - ".github/**" jobs: main: diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 102262b1413..7e7586b23d9 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -5,11 +5,7 @@ on: branches: - master workflow_dispatch: - pull_request: - paths: - - "gnovm/**" - - "go.*" # check on go.mod/sum update - - ".github/**" + pull_request: jobs: main: diff --git a/.github/workflows/lint_template.yml b/.github/workflows/lint_template.yml index c1330d0a3d0..65679633240 100644 --- a/.github/workflows/lint_template.yml +++ b/.github/workflows/lint_template.yml @@ -1,12 +1,12 @@ on: workflow_call: - inputs: - modulepath: - required: true - type: string - go-version: - required: true - type: string + inputs: + modulepath: + required: true + type: string + go-version: + required: true + type: string jobs: @@ -20,8 +20,9 @@ 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: --config=${{ github.workspace }}/.github/golangci.yml + version: v1.59 # sync with misc/devdeps \ No newline at end of file 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/nightlies.yml b/.github/workflows/nightlies.yml index a7a74f5bfa6..e8f3fe4ca5c 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -23,8 +23,8 @@ jobs: go-version: "1.22.x" cache: true - - uses: sigstore/cosign-installer@v3.5.0 - - uses: anchore/sbom-action/download-syft@v0.15.10 + - uses: sigstore/cosign-installer@v3.6.0 + - uses: anchore/sbom-action/download-syft@v0.17.2 - uses: docker/login-action@v3 with: @@ -35,11 +35,12 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - uses: goreleaser/goreleaser-action@v5 + - uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: v1.26.2-pro - args: release --clean --nightly --config ./.github/goreleaser-nightly.yaml + version: ~> v2 + args: release --clean --nightly --config ./.github/goreleaser.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + TAG_VERSION: nightly diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index 68413a942a9..b81957b22db 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -1,13 +1,17 @@ name: portal-loop on: + pull_request: + branches: + - master push: paths: - "misc/loop/**" - ".github/workflows/portal-loop.yml" branches: - "master" - - "ops/portal-loop" + # NOTE(albttx): branch name to simplify tests for this workflow + - "ci/portal-loop" tags: - "v*" @@ -39,10 +43,64 @@ 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 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + test-portal-loop-docker-compose: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Setup the images" + run: | + cd misc/loop + + docker compose build + docker compose pull + docker compose up -d + + - name: "Test1 - Portal loop start gnoland" + run: | + while + block_height=$(curl -s localhost:26657/status | jq -r '.result.sync_info.latest_block_height') + echo "Current block height: $block_height" + [[ "$block_height" -lt 10 ]] + do + sleep 1 + done + + curl -s localhost:26657/status | jq + + - name: "Buid new gnolang/gno image" + run: | + docker build -t ghcr.io/gnolang/gno/gnoland:master -f Dockerfile --target gnoland . + + - name: "Wait for new docker image" + run: | + ip_addr=$(cat misc/loop/traefik/gno.yml | grep -o "http://.*:26657") + while + new_ip_addr=$(cat misc/loop/traefik/gno.yml | grep -o "http://.*:26657") + echo "${ip_addr} -> ${new_ip_addr}" + [[ "${ip_addr}" == ${new_ip_addr} ]] + do + sleep 5 + done + + - name: "Test2 - Wait portal-loop start new image" + run: | + while + block_height=$(curl -s localhost:26657/status | jq -r '.result.sync_info.latest_block_height') + echo "Current block height: $block_height" + [[ "$block_height" -lt 10 ]] + do + sleep 5 + done + docker ps -a + curl -s localhost:26657/status | jq diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 71cad8af8ee..7eda0536532 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -24,8 +24,8 @@ jobs: go-version: "1.22.x" cache: true - - uses: sigstore/cosign-installer@v3.5.0 - - uses: anchore/sbom-action/download-syft@v0.15.10 + - uses: sigstore/cosign-installer@v3.6.0 + - uses: anchore/sbom-action/download-syft@v0.17.2 - uses: docker/login-action@v3 with: @@ -36,11 +36,12 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - uses: goreleaser/goreleaser-action@v5 + - uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: v1.26.2-pro - args: release --clean --nightly --config ./.github/goreleaser-master.yaml + version: ~> v2 + args: release --clean --nightly --config ./.github/goreleaser.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + TAG_VERSION: master diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml index bfb8f57c77b..5433582cace 100644 --- a/.github/workflows/releaser.yml +++ b/.github/workflows/releaser.yml @@ -23,8 +23,8 @@ jobs: go-version: "1.22.x" cache: true - - uses: sigstore/cosign-installer@v3.5.0 - - uses: anchore/sbom-action/download-syft@v0.15.10 + - uses: sigstore/cosign-installer@v3.6.0 + - uses: anchore/sbom-action/download-syft@v0.17.2 - uses: docker/login-action@v3 with: @@ -35,10 +35,10 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - uses: goreleaser/goreleaser-action@v5 + - uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: v1.26.2-pro + version: ~> v2 args: release --clean --config ./.github/goreleaser.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index c6b7207bc53..18911415087 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -25,15 +25,44 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Go test - run: go test -coverprofile coverage.out -covermode=atomic -timeout ${{ inputs.tests-timeout }} -v ./... working-directory: ${{ inputs.modulepath }} - - name: Upload coverage to Codecov + env: + TXTARCOVERDIR: /tmp/txtarcoverdir # txtar cover output + GOCOVERDIR: /tmp/gocoverdir # go cover output + COVERDIR: /tmp/coverdir # final output + run: | + set -x # print commands + + mkdir -p "$GOCOVERDIR" "$TXTARCOVERDIR" "$COVERDIR" + + # Craft a filter flag based on the module path to avoid expanding coverage on unrelated tags. + export filter="-pkg=github.com/gnolang/gno/${{ inputs.modulepath }}/..." + + # XXX: Simplify coverage of txtar - the current setup is a bit + # confusing and meticulous. There will be some improvements in Go + # 1.23 regarding coverage, so we can use this as a workaround until + # then. + go test -covermode=atomic -timeout ${{ inputs.tests-timeout }} -v ./... -test.gocoverdir=$GOCOVERDIR + + # Print results + (set +x; echo 'go coverage results:') + go tool covdata percent $filter -i=$GOCOVERDIR + (set +x; echo 'txtar coverage results:') + go tool covdata percent $filter -i=$TXTARCOVERDIR + + # Generate final coverage output + go tool covdata textfmt -v 1 $filter -i=$GOCOVERDIR,$TXTARCOVERDIR -o gocoverage.out + + - name: Upload go coverage to Codecov uses: codecov/codecov-action@v4 with: - token: ${{ secrets.codecov-token }} - verbose: true - fail_ci_if_error: true - flags: ${{ inputs.modulepath }} + disable_search: true + fail_ci_if_error: true + file: ${{ inputs.modulepath }}/gocoverage.out + flags: ${{ inputs.modulepath }} + token: ${{ secrets.codecov-token }} + verbose: true # keep this enable as it help debugging when coverage fail randomly on the CI + # TODO: We have to fix race conditions before running this job # test-with-race: # runs-on: ubuntu-latest diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 81b7abf27f9..57e84793c94 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -6,10 +6,6 @@ on: - master workflow_dispatch: pull_request: - paths: - - "tm2/**" - - "go.*" # check on go.mod/sum update - - ".github/**" jobs: main: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..fa5a9e47270 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# build gno +FROM golang:1.22-alpine AS build-gno +RUN go env -w GOMODCACHE=/root/.cache/go-build +WORKDIR /gnoroot +ENV GNOROOT="/gnoroot" +COPY . ./ +RUN --mount=type=cache,target=/root/.cache/go-build go mod download +RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./build/gnoland ./gno.land/cmd/gnoland +RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./build/gnokey ./gno.land/cmd/gnokey +RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./build/gnoweb ./gno.land/cmd/gnoweb +RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./build/gno ./gnovm/cmd/gno + +# Base image +FROM alpine:3.17 AS base +WORKDIR /gnoroot +ENV GNOROOT="/gnoroot" +RUN apk add ca-certificates +CMD [ "" ] + +# alpine images +# gnoland +FROM base AS gnoland +COPY --from=build-gno /gnoroot/build/gnoland /usr/bin/gnoland +COPY --from=build-gno /gnoroot/examples /gnoroot/examples +COPY --from=build-gno /gnoroot/gnovm/stdlibs /gnoroot/gnovm/stdlibs +COPY --from=build-gno /gnoroot/gno.land/genesis/genesis_txs.jsonl /gnoroot/gno.land/genesis/genesis_txs.jsonl +COPY --from=build-gno /gnoroot/gno.land/genesis/genesis_balances.txt /gnoroot/gno.land/genesis/genesis_balances.txt +EXPOSE 26656 26657 +ENTRYPOINT ["/usr/bin/gnoland"] + +# gnokey +FROM base AS gnokey +COPY --from=build-gno /gnoroot/build/gnokey /usr/bin/gnokey +# gofmt is required by `gnokey maketx addpkg` +COPY --from=build-gno /usr/local/go/bin/gofmt /usr/bin/gofmt +ENTRYPOINT ["/usr/bin/gnokey"] + +# gno +FROM base AS gno +COPY --from=build-gno /gnoroot/build/gno /usr/bin/gno +ENTRYPOINT ["/usr/bin/gno"] + +# gnoweb +FROM base AS gnoweb +COPY --from=build-gno /gnoroot/build/gnoweb /usr/bin/gnoweb +COPY --from=build-gno /opt/gno/src/gno.land/cmd/gnoweb /opt/gno/src/gnoweb +EXPOSE 8888 +ENTRYPOINT ["/usr/bin/gnoweb"] + +# all, contains everything. +FROM base AS all +COPY --from=build-gno /gnoroot/build/* /usr/bin/ +COPY --from=build-gno /gnoroot/examples /gnoroot/examples +COPY --from=build-gno /gnoroot/gnovm/stdlibs /gnoroot/gnovm/stdlibs +COPY --from=build-gno /gnoroot/gno.land/genesis/genesis_txs.jsonl /gnoroot/gno.land/genesis/genesis_txs.jsonl +COPY --from=build-gno /gnoroot/gno.land/genesis/genesis_balances.txt /gnoroot/gno.land/genesis/genesis_balances.txt +# 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 index 2e36453382e..644f8cb5de9 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -35,6 +35,13 @@ COPY ./gnoweb /usr/bin/gnoweb EXPOSE 8888 ENTRYPOINT [ "/usr/bin/gnoweb" ] +# +## ghcr.io/gnolang/gno/gnofaucet +FROM base as gnofaucet + +COPY ./gnofaucet /usr/bin/gnofaucet +EXPOSE 5050 +ENTRYPOINT [ "/usr/bin/gnofaucet" ] # ## ghcr.io/gnolang/gno diff --git a/contribs/gnodev/Makefile b/contribs/gnodev/Makefile index 01801064d1f..df57040d92d 100644 --- a/contribs/gnodev/Makefile +++ b/contribs/gnodev/Makefile @@ -5,11 +5,13 @@ GOTEST_FLAGS ?= $(GOBUILD_FLAGS) -v -p 1 -timeout=5m rundep := go run -modfile ../../misc/devdeps/go.mod golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint -install: +install: install.gnodev +install.gnodev: go install $(GOBUILD_FLAGS) ./cmd/gnodev -build: - go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gnodev +# keep gnobro out the default install for now +install.gnobro: + go install $(GOBUILD_FLAGS) ./cmd/gnobro lint: $(golangci_lint) --config ../../.github/golangci.yml run ./... diff --git a/contribs/gnodev/README.md b/contribs/gnodev/README.md index 6da9e7b1ebc..3b26903c7eb 100644 --- a/contribs/gnodev/README.md +++ b/contribs/gnodev/README.md @@ -1,30 +1,67 @@ -## `gnodev`: Your Gno Companion Tool +## `gnodev`: Your Gno Development Companion -`gnodev` is designed to be a robust and user-friendly tool in your realm package development journey, streamlining your workflow and enhancing productivity. +`gnodev` is a robust tool designed to streamline your Gno package development process, enhancing productivity +by providing immediate feedback on code changes. -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). +Please note that this is a quick overview. For a more detailed guide, refer to the official documentation at +[docs/gno-tooling/gnodev.md](../../docs/gno-tooling/cli/gnodev.md). ### Synopsis -**gnodev** [**-minimal**] [**-no-watch**] [**PKG_PATH ...**] +**gnodev** [**options**] [**PKG_PATH ...**] ### Features -- **In-Memory Node**: Gnodev starts an in-memory node, and automatically loads - the **examples** folder and any user-specified paths. -- **Web Interface Server**: Starts a `gnoweb` server on `localhost:8888`. -- **Hot Reload**: Monitors the example packages folder and specified directories for file changes, - reloading the package and automatically restarting the node as needed. -- **State Maintenance**: Ensures the current state is preserved by replaying all transactions. +- **In-Memory Node**: Gnodev starts an in-memory node, automatically loading the **examples** folder and any + user-specified paths. +- **Web Interface Server**: Gnodev starts a `gnoweb` server on [`localhost:8888`](https://localhost:8888). +- **Balances and Keybase Customization**: Set account balances, load them from a file, or add new accounts via a flag. +- **Hot Reload**: Monitors the **examples** folder and specified directories for file changes, reloading the + package and automatically restarting the node as needed. +- **State Maintenance**: Ensures the previous node state is preserved by replaying all transactions. +- **Transaction Manipulation**: Allows for interactive cancellation and redoing of transactions. +- **State Export**: Export the current state at any time in a genesis doc format. ### Commands -While `gnodev` is running, the user can trigger specific actions by pressing -the following combinations: -- **H**: Display help information. -- **R**: Reload the node, without resetting the state. -- **Ctrl+R**: Reset the current node state. -- **Ctrl+C**: Exit `gnodev`. +While `gnodev` is running, trigger specific actions by pressing the following combinations: +- **H**: Display help information. +- **A**: Display account balances. +- **R**: Reload the node manually. +- **P**: Cancel the last action. +- **N**: Redo the last cancelled action. +- **Ctrl+S**: Save the current state. +- **Ctrl+R**: Restore the saved state. +- **E**: Export the current state to a genesis file. +- **Cmd+R**: Reset the current node state. +- **Cmd+C**: Exit `gnodev`. + +### Usage +Run `gnodev` followed by any specific options and/or package paths. The **examples** directory is loaded +automatically. Use `--minimal` to prevent this. + +Example: +``` +gnodev --add-account [:] ./myrealm +``` + +### `gnobro`: realm interface +`gnobro` is a terminal user interface (TUI) that allows you to browse realms within your terminal. It +automatically connects to `gnodev` for real-time development. In addition to hot reload, it also has the +ability to execute commands and interact with your realm. + + +#### Usage +**gnobro** [**options**] [**PKG_PATH **] + +Run gnobro followed by any specific options and/or a target pacakge path. + +Use `gnobro -h` for a detailed list of options. + +Example: +``` +gnobro gno.land/r/demo/home +``` -### Loading 'examples' -The **examples** directory is loaded automatically. If working within this folder, you don't have to specify any additional paths to `gnodev`. Use `--minimal` to prevent this. ### Installation Run `make install` to install `gnodev`. + +Run `make install.gnobro` to install `gnobro`. diff --git a/contribs/gnodev/cmd/gnobro/assets/banner_land_1.txt b/contribs/gnodev/cmd/gnobro/assets/banner_land_1.txt new file mode 100644 index 00000000000..9a5931625ca --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/assets/banner_land_1.txt @@ -0,0 +1,19 @@ + . + + + . Hello %s, Welcome to + . ++ .,-:::::/ :::. :::. ... . + . ,;;-'````' `;;;;, `;;; .;;;;;;;. + [[[ [[[[[[/ [[[[[. '[[ ,[[ \[[, + "$$c. "$$ + $$$ "Y$c$$ $$$, $$$ + `Y8bo,,,o88o 888 Y88 "888,_ _,88P + . `'YMUP"YMM MMM . YM "YMMMMMP" + + . . + ::: + :::. :::. :::. :::::::-. + ;;; ;;`;; `;;;;, `;;; ;;, `';, + + [[[ ,[[ '[[, + [[[[[. '[[ `[[ [[ + $$' c$$$cc$$$c $$$ "Y$c$$ $$, $$ + o88oo,.__ 888 888, 888 Y88 888_,o8P' + """"YUMMM YMM ""` MMM + YM MMMMP"` + + . + + + press to continue diff --git a/contribs/gnodev/cmd/gnobro/assets/gn_hc1.utf8ans b/contribs/gnodev/cmd/gnobro/assets/gn_hc1.utf8ans new file mode 100644 index 00000000000..b50762d0abb --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/assets/gn_hc1.utf8ans @@ -0,0 +1,25 @@ + · . · · + . * . . * . · + · · · · . . . + · . . . . · * + . · . . · + ░░ ░░ ░ ░░ ░ ░░ ░ ░░ ░░░ ░░░░░░ ░░░ ░░░░ ░ ░░ +░░▒▒░░░▒▒░░░░░▒░░░░░░▒▒░░▒░░▒▒░▒░░░░░░░ ░░░░░░▒▒░▒▒▒░▒▒▒▒▒▒░░▒▒▒░░░▒▒▒▒░▒░░▒▒░░ +▒▒▓▓▒▒▒▓▓▒▒▒▒▒▓▒▒▒▒▒▒▓▓▒▒▓▒▒▓▓▒▓▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓▒▓▓▓▒▓▓▓▓▓▓▒▒▓▓▓▒▒▒▓▓▓▓▒▓▒▒▓▓▒▒ +▓▓██▓▓▓██▓▓▓▓▓█▓▓▓▓▓▓██▓▓█▓▓██▓█▓▓▓▓▓▓▓▒▓▓▓▓▓▓██▓███▓██████▓▓███▓▓▓████▓█▓▓██▓▓ +▀██▒███ █████ ██████▌▐██▒██▌██▒███████▓███▓██▌▐██▒██▌██▒█████▒████ ███▒███ ██ + ▐▒▒▒█ █▒█▌ ▐▒███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███▌███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███ █▒▒▒█▌ ▐▒ + ▒▒▒▒▄ ▄▒█ ▒█▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒██▐ █▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒ █▄ ▄▒▒ ▒ ▒ + ░ ░ ▄ ▄▄▒▌ ▒█▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒▄▀▀▄▐▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒ █ ▄ ▄▄▄▀▀▄ ▒ + ▀░ ▒░▒▄░ ▀▄▒▀▒▒ ▀█▒▄▄ ▄■▄▒▒▀ ▒▌░░▐░▀ ▒ ▀ ▄ ■▒░▀ ▒ ▀░ ▒░▌░░▐░ ▀▄ + ▌░░▐ ▌░░▐ + ▄▄▀▀▀▀▀▀▀▄▄▀▀▄▀▀▀▀▀▄ ▄▀▀▀▀▀▄ ▌▒▒▐ ▄▄▀▀▀▀▀▄ ▄▀▄▄▀▀▀▀▀▄ ▄▄▄▄▄▌▒▒▐ + ▌▄▓▓▓▓▓▓▓▄ ▓▓▄▓▓▓▓▓▄▀▀▄▓▓▓▓▓▄▀▄ ▌▓▓▐ ▌▄▓▓▓▓▓▄▀▌▓▌▄▓▓▓▓▓▄▀▀▄▄▄▄▄▄▓▓▐ + ▌█▌ ██ ███▀▄▄▀██ ▐██▀▀▀██▌▐ ▌██▐ ▐▐██▀▀▀██▌ ███▀▄▄▀██ ▐██▀▀▀███▐ + ▌▀░░░░░░░░ ░░▌▌ ▌░░ ░░ ░░▐ ▌░░▐ ▌░░ ░░ ░░▌█ ▌░░ ░░▌ ▐░░▐ + ▐ ▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐▄▀▄▌▒▒▐ ▌▒▒▌ ▐▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐ + ▌▄▄ ▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▓▓▓ ▄▓▄ ▓▓ ▀▀ ▓▓▓ ▐▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▐▓▓▐ +░ ▌▀███████▀ ███▐ ▐▐██ ▄▀█████▀▄▄▀█▀▄▀████ ▄▀████▀██ ███▐ ▐▐██ ▄▀████▀██▐ ░ +▒ ▓▓▄▄▄▄▄▄▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▄▀ ▀▄▀ ▀▄▄▄▄▀ ▀▄▄▄▄▀▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▀▄▄▓ ▒ +▓ ▓ +█▓▒░ ░▒▓█ \ No newline at end of file diff --git a/contribs/gnodev/cmd/gnobro/assets/gn_hc2.utf8ans b/contribs/gnodev/cmd/gnobro/assets/gn_hc2.utf8ans new file mode 100644 index 00000000000..12a3a9a236f --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/assets/gn_hc2.utf8ans @@ -0,0 +1,25 @@ + · . · · + . + . . + . · + · · · · . . . + · . . . . · + + . · . . · + ░░ ░░ ░ ░░ ░ ░░ ░ ░░ ░░░ ░░░░░░ ░░░ ░░░░ ░ ░░ +░░▒▒░░░▒▒░░░░░▒░░░░░░▒▒░░▒░░▒▒░▒░░░░░░░ ░░░░░░▒▒░▒▒▒░▒▒▒▒▒▒░░▒▒▒░░░▒▒▒▒░▒░░▒▒░░ +▒▒▓▓▒▒▒▓▓▒▒▒▒▒▓▒▒▒▒▒▒▓▓▒▒▓▒▒▓▓▒▓▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓▒▓▓▓▒▓▓▓▓▓▓▒▒▓▓▓▒▒▒▓▓▓▓▒▓▒▒▓▓▒▒ +▓▓██▓▓▓██▓▓▓▓▓█▓▓▓▓▓▓██▓▓█▓▓██▓█▓▓▓▓▓▓▓▒▓▓▓▓▓▓██▓███▓██████▓▓███▓▓▓████▓█▓▓██▓▓ +▀██▒███ █████ ██████▌▐██▒██▌██▒███████▓███▓██▌▐██▒██▌██▒█████▒████ ███▒███ ██ + ▐▒▒▒█ █▒█▌ ▐▒███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███▌███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███ █▒▒▒█▌ ▐▒ + ▒▒▒▒▄ ▄▒█ ▒█▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒██▐ █▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒ █▄ ▄▒▒ ▒ ▒ + ░ ░ ▄ ▄▄▒▌ ▒█▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒▄▀▀▄▐▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒ █ ▄ ▄▄▄▀▀▄ ▒ + ▀░ ▒░▒▄░ ▀▄▒▀▒▒ ▀█▒▄▄ ▄■▄▒▒▀ ▒▌░░▐░▀ ▒ ▀ ▄ ■▒░▀ ▒ ▀░ ▒░▌░░▐░ ▀▄ + ▌░░▐ ▌░░▐ + ▄▄▀▀▀▀▀▀▀▄▄▀▀▄▀▀▀▀▀▄ ▄▀▀▀▀▀▄ ▌▒▒▐ ▄▄▀▀▀▀▀▄ ▄▀▄▄▀▀▀▀▀▄ ▄▄▄▄▄▌▒▒▐ + ▌▄▓▓▓▓▓▓▓▄ ▓▓▄▓▓▓▓▓▄▀▀▄▓▓▓▓▓▄▀▄ ▌▓▓▐ ▌▄▓▓▓▓▓▄▀▌▓▌▄▓▓▓▓▓▄▀▀▄▄▄▄▄▄▓▓▐ + ▌█▌ ██ ███▀▄▄▀██ ▐██▀▀▀██▌▐ ▌██▐ ▐▐██▀▀▀██▌ ███▀▄▄▀██ ▐██▀▀▀███▐ + ▌▀░░░░░░░░ ░░▌▌ ▌░░ ░░ ░░▐ ▌░░▐ ▌░░ ░░ ░░▌█ ▌░░ ░░▌ ▐░░▐ + ▐ ▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐▄▀▄▌▒▒▐ ▌▒▒▌ ▐▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐ + ▌▄▄ ▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▓▓▓ ▄▓▄ ▓▓ ▀▀ ▓▓▓ ▐▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▐▓▓▐ +░ ▌▀███████▀ ███▐ ▐▐██ ▄▀█████▀▄▄▀█▀▄▀████ ▄▀████▀██ ███▐ ▐▐██ ▄▀████▀██▐ ░ +▒ ▓▓▄▄▄▄▄▄▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▄▀ ▀▄▀ ▀▄▄▄▄▀ ▀▄▄▄▄▀▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▀▄▄▓ ▒ +▓ ▓ +█▓▒░ ░▒▓█ \ No newline at end of file diff --git a/contribs/gnodev/cmd/gnobro/assets/gn_hc3.utf8ans b/contribs/gnodev/cmd/gnobro/assets/gn_hc3.utf8ans new file mode 100644 index 00000000000..ff26c68f964 --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/assets/gn_hc3.utf8ans @@ -0,0 +1,25 @@ + · . · · + . · . . · . · + · · · · . . . + · . . . . · · + . · . . · + ░░ ░░ ░ ░░ ░ ░░ ░ ░░ ░░░ ░░░░░░ ░░░ ░░░░ ░ ░░ +░░▒▒░░░▒▒░░░░░▒░░░░░░▒▒░░▒░░▒▒░▒░░░░░░░ ░░░░░░▒▒░▒▒▒░▒▒▒▒▒▒░░▒▒▒░░░▒▒▒▒░▒░░▒▒░░ +▒▒▓▓▒▒▒▓▓▒▒▒▒▒▓▒▒▒▒▒▒▓▓▒▒▓▒▒▓▓▒▓▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓▒▓▓▓▒▓▓▓▓▓▓▒▒▓▓▓▒▒▒▓▓▓▓▒▓▒▒▓▓▒▒ +▓▓██▓▓▓██▓▓▓▓▓█▓▓▓▓▓▓██▓▓█▓▓██▓█▓▓▓▓▓▓▓▒▓▓▓▓▓▓██▓███▓██████▓▓███▓▓▓████▓█▓▓██▓▓ +▀██▒███ █████ ██████▌▐██▒██▌██▒███████▓███▓██▌▐██▒██▌██▒█████▒████ ███▒███ ██ + ▐▒▒▒█ █▒█▌ ▐▒███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███▌███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███ █▒▒▒█▌ ▐▒ + ▒▒▒▒▄ ▄▒█ ▒█▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒██▐ █▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒ █▄ ▄▒▒ ▒ ▒ + ░ ░ ▄ ▄▄▒▌ ▒█▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒▄▀▀▄▐▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒ █ ▄ ▄▄▄▀▀▄ ▒ + ▀░ ▒░▒▄░ ▀▄▒▀▒▒ ▀█▒▄▄ ▄■▄▒▒▀ ▒▌░░▐░▀ ▒ ▀ ▄ ■▒░▀ ▒ ▀░ ▒░▌░░▐░ ▀▄ + ▌░░▐ ▌░░▐ + ▄▄▀▀▀▀▀▀▀▄▄▀▀▄▀▀▀▀▀▄ ▄▀▀▀▀▀▄ ▌▒▒▐ ▄▄▀▀▀▀▀▄ ▄▀▄▄▀▀▀▀▀▄ ▄▄▄▄▄▌▒▒▐ + ▌▄▓▓▓▓▓▓▓▄ ▓▓▄▓▓▓▓▓▄▀▀▄▓▓▓▓▓▄▀▄ ▌▓▓▐ ▌▄▓▓▓▓▓▄▀▌▓▌▄▓▓▓▓▓▄▀▀▄▄▄▄▄▄▓▓▐ + ▌█▌ ██ ███▀▄▄▀██ ▐██▀▀▀██▌▐ ▌██▐ ▐▐██▀▀▀██▌ ███▀▄▄▀██ ▐██▀▀▀███▐ + ▌▀░░░░░░░░ ░░▌▌ ▌░░ ░░ ░░▐ ▌░░▐ ▌░░ ░░ ░░▌█ ▌░░ ░░▌ ▐░░▐ + ▐ ▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐▄▀▄▌▒▒▐ ▌▒▒▌ ▐▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐ + ▌▄▄ ▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▓▓▓ ▄▓▄ ▓▓ ▀▀ ▓▓▓ ▐▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▐▓▓▐ +░ ▌▀███████▀ ███▐ ▐▐██ ▄▀█████▀▄▄▀█▀▄▀████ ▄▀████▀██ ███▐ ▐▐██ ▄▀████▀██▐ ░ +▒ ▓▓▄▄▄▄▄▄▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▄▀ ▀▄▀ ▀▄▄▄▄▀ ▀▄▄▄▄▀▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▀▄▄▓ ▒ +▓ ▓ +█▓▒░ ░▒▓█ \ No newline at end of file diff --git a/contribs/gnodev/cmd/gnobro/assets/gn_hc4.utf8ans b/contribs/gnodev/cmd/gnobro/assets/gn_hc4.utf8ans new file mode 100644 index 00000000000..b92dee86843 --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/assets/gn_hc4.utf8ans @@ -0,0 +1,25 @@ + · . · · + . · . . · . · + · · · · . . . + · . . . . · · + . · . . · + ░░ ░░ ░ ░░ ░ ░░ ░ ░░ ░░░ ░░░░░░ ░░░ ░░░░ ░ ░░ +░░▒▒░░░▒▒░░░░░▒░░░░░░▒▒░░▒░░▒▒░▒░░░░░░░ ░░░░░░▒▒░▒▒▒░▒▒▒▒▒▒░░▒▒▒░░░▒▒▒▒░▒░░▒▒░░ +▒▒▓▓▒▒▒▓▓▒▒▒▒▒▓▒▒▒▒▒▒▓▓▒▒▓▒▒▓▓▒▓▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓▒▓▓▓▒▓▓▓▓▓▓▒▒▓▓▓▒▒▒▓▓▓▓▒▓▒▒▓▓▒▒ +▓▓██▓▓▓██▓▓▓▓▓█▓▓▓▓▓▓██▓▓█▓▓██▓█▓▓▓▓▓▓▓▒▓▓▓▓▓▓██▓███▓██████▓▓███▓▓▓████▓█▓▓██▓▓ +▀██▒███ █████ ██████▌▐██▒██▌██▒███████▓███▓██▌▐██▒██▌██▒█████▒████ ███▒███ ██ + ▐▒▒▒█ █▒█▌ ▐▒███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███▌███▌ ▐▒▒▒▌ ▐▒▒███▌█▒███ █▒▒▒█▌ ▐▒ + ▒▒▒▒▄ ▄▒█ ▒█▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒██▐ █▒█▄ ▒▒▒░ ▀▒▒█▌ ▐▒ █▄ ▄▒▒ ▒ ▒ + ░ ░ ▄ ▄▄▒▌ ▒█▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒▄▀▀▄▐▒▀ ▀▒▒ ▒▒▒▀▄ ▄▒ █ ▄ ▄▄▄▀▀▄ ▒ + ▀░ ▒░▒▄░ ▀▄▒▀▒▒ ▀█▒▄▄ ▄■▄▒▒▀ ▒▌░░▐░▀ ▒ ▀ ▄ ■▒░▀ ▒ ▀░ ▒░▌░░▐░ ▀▄ + ▌░░▐ ▌░░▐ + ▄▄▀▀▀▀▀▀▀▄▄▀▀▄▀▀▀▀▀▄ ▄▀▀▀▀▀▄ ▌▒▒▐ ▄▄▀▀▀▀▀▄ ▄▀▄▄▀▀▀▀▀▄ ▄▄▄▄▄▌▒▒▐ + ▌▄▓▓▓▓▓▓▓▄ ▓▓▄▓▓▓▓▓▄▀▀▄▓▓▓▓▓▄▀▄ ▌▓▓▐ ▌▄▓▓▓▓▓▄▀▌▓▌▄▓▓▓▓▓▄▀▀▄▄▄▄▄▄▓▓▐ + ▌█▌ ██ ███▀▄▄▀██ ▐██▀▀▀██▌▐ ▌██▐ ▐▐██▀▀▀██▌ ███▀▄▄▀██ ▐██▀▀▀███▐ + ▌▀░░░░░░░░ ░░▌▌ ▌░░ ░░ ░░▐ ▌░░▐ ▌░░ ░░ ░░▌█ ▌░░ ░░▌ ▐░░▐ + ▐ ▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐▄▀▄▌▒▒▐ ▌▒▒▌ ▐▒▒ ▒▒▌▌ ▌▒▒ ▒▒▌ ▐▒▒▐ + ▌▄▄ ▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▓▓▓ ▄▓▄ ▓▓ ▀▀ ▓▓▓ ▐▓▓ ▓▓▌▌ ▌▓▓ ▓▓▓ ▐▓▓▐ +░ ▌▀███████▀ ███▐ ▐▐██ ▄▀█████▀▄▄▀█▀▄▀████ ▄▀████▀██ ███▐ ▐▐██ ▄▀████▀██▐ ░ +▒ ▓▓▄▄▄▄▄▄▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▄▀ ▀▄▀ ▀▄▄▄▄▀ ▀▄▄▄▄▀▄▄▀▄▄▄▀ ▀▄▄▀ ▀▄▄▄▄▀▄▄▓ ▒ +▓ ▓ +█▓▒░ ░▒▓█ \ No newline at end of file diff --git a/contribs/gnodev/cmd/gnobro/banner.go b/contribs/gnodev/cmd/gnobro/banner.go new file mode 100644 index 00000000000..311ee9bdb2a --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/banner.go @@ -0,0 +1,37 @@ +package main + +import ( + "embed" + "path/filepath" + "time" + + "github.com/gnolang/gno/contribs/gnodev/pkg/browser" +) + +//go:embed assets/*.utf8ans +var gnoland_banner embed.FS + +func NewGnoLandBanner() browser.ModelBanner { + const assets = "assets" + + entries, err := gnoland_banner.ReadDir(assets) + if err != nil { + panic("unable to banner dir: " + err.Error()) + } + + frames := make([]string, len(entries)) + for i, entry := range entries { + if entry.IsDir() { + continue + } + + frame, err := gnoland_banner.ReadFile(filepath.Join(assets, entry.Name())) + if err != nil { + panic("unable to read banner frame: " + err.Error()) + } + + frames[i] = string(frame) + } + + return browser.NewModelBanner(time.Second/3, frames) +} diff --git a/contribs/gnodev/cmd/gnobro/main.go b/contribs/gnodev/cmd/gnobro/main.go new file mode 100644 index 00000000000..092a441542a --- /dev/null +++ b/contribs/gnodev/cmd/gnobro/main.go @@ -0,0 +1,514 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "io" + "log/slog" + "net" + "net/url" + "os" + "os/signal" + "path/filepath" + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + charmlog "github.com/charmbracelet/log" + "github.com/charmbracelet/ssh" + "github.com/charmbracelet/wish" + "github.com/charmbracelet/wish/activeterm" + "github.com/charmbracelet/wish/bubbletea" + "golang.org/x/sync/errgroup" + + "github.com/gnolang/gno/contribs/gnodev/pkg/browser" + "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" +) + +const gnoPrefix = "gno.land" + +type broCfg struct { + readonly bool + remote string + dev bool + devRemote string + chainID string + defaultAccount string + defaultRealm string + sshListener string + sshHostKeyPath string + banner bool + jsonlog bool +} + +var defaultBroOptions = broCfg{ + remote: "127.0.0.1:26657", + dev: true, + devRemote: "", + sshListener: "", + defaultRealm: "gno.land/r/gnoland/home", + chainID: "dev", + sshHostKeyPath: ".ssh/id_ed25519", +} + +func main() { + cfg := &broCfg{} + + stdio := commands.NewDefaultIO() + cmd := commands.NewCommand( + commands.Metadata{ + Name: "gnobro", + ShortUsage: "gnobro [flags] [pkg_path]", + ShortHelp: "Gno Browser, a realm explorer", + LongHelp: `Gnobro is a terminal user interface (TUI) that allows you to browse realms within your +terminal. It automatically connects to Gnodev for real-time development. In +addition to hot reload, it also has the ability to execute commands and interact +with your realm. +`, + }, + cfg, + func(_ context.Context, args []string) error { + return execBrowser(cfg, args, stdio) + }) + + cmd.Execute(context.Background(), os.Args[1:]) +} + +func (c *broCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.remote, + "remote", + defaultBroOptions.remote, + "remote gno.land URL", + ) + + fs.StringVar( + &c.chainID, + "chainid", + defaultBroOptions.chainID, + "chainid", + ) + + fs.StringVar( + &c.defaultAccount, + "account", + defaultBroOptions.defaultAccount, + "default local account to use", + ) + + fs.StringVar( + &c.defaultRealm, + "default-realm", + defaultBroOptions.defaultRealm, + "default realm to display when gnobro starts and no argument is provided", + ) + + fs.StringVar( + &c.sshListener, + "ssh", + defaultBroOptions.sshListener, + "ssh server listener address", + ) + + fs.StringVar( + &c.sshHostKeyPath, + "ssh-key", + defaultBroOptions.sshHostKeyPath, + "ssh host key path", + ) + + fs.BoolVar( + &c.dev, + "dev", + defaultBroOptions.dev, + "enable dev mode and connect to gnodev for realtime update", + ) + + fs.StringVar( + &c.devRemote, + "dev-remote", + defaultBroOptions.devRemote, + "dev endpoint, if empty will default to `ws://:8888`", + ) + + fs.BoolVar( + &c.banner, + "banner", + defaultBroOptions.banner, + "if enabled, display a banner", + ) + + fs.BoolVar( + &c.readonly, + "readonly", + defaultBroOptions.readonly, + "readonly mode, no commands allowed", + ) + + fs.BoolVar( + &c.jsonlog, + "jsonlog", + defaultBroOptions.jsonlog, + "display server log as json format", + ) +} + +func execBrowser(cfg *broCfg, args []string, cio commands.IO) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + home := gnoenv.HomeDir() + + var address string + var kb keys.Keybase + if cfg.defaultAccount != "" { + address = cfg.defaultAccount + + var err error + kb, err = keys.NewKeyBaseFromDir(home) + if err != nil { + return fmt.Errorf("unable to load keybase: %w", err) + } + } else { + // create a inmemory keybase + kb = keys.NewInMemory() + kb.CreateAccount(integration.DefaultAccount_Name, integration.DefaultAccount_Seed, "", "", 0, 0) + address = integration.DefaultAccount_Name + } + + signer, err := getSignerForAccount(cio, address, kb, cfg) + if err != nil { + return fmt.Errorf("unable to get signer for account %q: %w", address, err) + } + + cl, err := client.NewHTTPClient(cfg.remote) + if err != nil { + return fmt.Errorf("unable to create http client for %q: %w", cfg.remote, err) + } + + gnocl := &gnoclient.Client{ + RPCClient: cl, + Signer: signer, + } + + var path string + switch { + case len(args) > 0: + path = strings.TrimSpace(args[0]) + path = strings.TrimPrefix(path, gnoPrefix) + case cfg.defaultRealm != "": + path = strings.TrimLeft(cfg.defaultRealm, gnoPrefix) + } + + bcfg := browser.DefaultConfig() + bcfg.Readonly = cfg.readonly + bcfg.Renderer = lipgloss.DefaultRenderer() + bcfg.URLDefaultValue = path + bcfg.URLPrefix = gnoPrefix + bcfg.URLPrefix = gnoPrefix + + if cfg.sshListener == "" { + if cfg.banner { + bcfg.Banner = NewGnoLandBanner() + } + + return runLocal(ctx, gnocl, cfg, bcfg, cio) + } + + return runServer(ctx, gnocl, cfg, bcfg, cio) +} + +func runLocal(ctx context.Context, gnocl *gnoclient.Client, cfg *broCfg, bcfg browser.Config, io commands.IO) error { + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + + model := browser.New(bcfg, gnocl) + p := tea.NewProgram(model, + tea.WithContext(ctx), + tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer" + tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel + ) + + var errgs errgroup.Group + + if cfg.dev { + devpoint, err := getDevEndpoint(cfg) + if err != nil { + return fmt.Errorf("unable to parse dev endpoint: %w", err) + } + + var devcl browser.DevClient + devcl.Handler = func(typ events.Type, data any) error { + switch typ { + case events.EvtReload, events.EvtReset, events.EvtTxResult: + p.Send(browser.RefreshRealm()) + default: + } + + return nil + } + + errgs.Go(func() error { + defer cancel() + + if err := devcl.Run(ctx, devpoint, nil); err != nil { + return fmt.Errorf("dev connection failed: %w", err) + } + + return nil + }) + } + + errgs.Go(func() error { + defer cancel() + + _, err := p.Run() + return err + }) + + if err := errgs.Wait(); err != nil && !errors.Is(err, context.Canceled) { + return err + } + + io.Println("Bye!") + return nil +} + +func runServer(ctx context.Context, gnocl *gnoclient.Client, cfg *broCfg, bcfg browser.Config, io commands.IO) error { + // setup logger + logger := newLogger(io.Out(), cfg.jsonlog) + + teaHandler := func(s ssh.Session) (tea.Model, []tea.ProgramOption) { + shortid := fmt.Sprintf("%.10s", s.Context().SessionID()) + + bcfgCopy := bcfg // copy config + + bcfgCopy.Logger = logger.WithGroup(shortid) + bcfgCopy.Renderer = bubbletea.MakeRenderer(s) + + if cfg.banner { + bcfgCopy.Banner = NewGnoLandBanner() + } + + pval := s.Context().Value("path") + if path, ok := pval.(string); ok && len(path) > 0 { + // Erase banner on specifc command + bcfgCopy.Banner = browser.ModelBanner{} + // Set up url + bcfgCopy.URLDefaultValue = path + } + + bcfgCopy.Logger.Info("session started", + "time", time.Now(), + "path", bcfgCopy.URLDefaultValue, + "sid", s.Context().SessionID(), + "user", s.User()) + model := browser.New(bcfgCopy, gnocl) + + return model, []tea.ProgramOption{ + tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer" + tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel + } + } + + sshaddr, err := net.ResolveTCPAddr("", cfg.sshListener) + if err != nil { + return fmt.Errorf("unable to resolve address: %w", err) + } + + s, err := wish.NewServer( + wish.WithAddress(sshaddr.String()), + wish.WithHostKeyPath(cfg.sshHostKeyPath), + wish.WithMiddleware( + bubbletea.Middleware(teaHandler), + activeterm.Middleware(), // ensure PTY + ValidatePathCommandMiddleware(bcfg.URLPrefix), + StructuredMiddlewareWithLogger( + ctx, logger, slog.LevelInfo, + ), + // XXX: add ip throttler + ), + ) + + var errgs errgroup.Group + + errgs.Go(func() error { + logger.Info("starting SSH server", "addr", sshaddr.String()) + return s.ListenAndServe() + }) + + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + + errgs.Go(func() error { + <-ctx.Done() + + logger.Info("stopping SSH server... (5s timeout)") + + sctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return s.Shutdown(sctx) + }) + + if err := errgs.Wait(); err != nil && !errors.Is(err, ssh.ErrServerClosed) { + return err + } + + if !cfg.jsonlog { + io.Println("Bye!") + } + return nil +} + +func getDevEndpoint(cfg *broCfg) (string, error) { + var err error + + // use remote address as default + host, port := cfg.remote, "8888" + if cfg.devRemote != "" { + // if any dev endpoint as been set, fallback on this + host, port, err = net.SplitHostPort(cfg.devRemote) + if err != nil { + return "", fmt.Errorf("unable to parse dev endpoint: %w", err) + } + } + + // ensure having a (any) protocol scheme + if !strings.Contains(host, "://") { + host = "http://" + host + } + + // parse full host including port + devpoint, err := url.Parse(host) + if err != nil { + return "", fmt.Errorf("unable to construct devaddr: %w", err) + } + + host, _, _ = net.SplitHostPort(devpoint.Host) + if port != "" { + devpoint.Host = host + ":" + port + } else { + devpoint.Host = host + } + + switch devpoint.Scheme { + case "ws", "wss": // already good + case "https": + devpoint.Scheme = "wss" + default: + devpoint.Scheme = "ws" + } + devpoint.Path = "_events" + + return devpoint.String(), nil +} + +func getSignerForAccount(io commands.IO, address string, kb keys.Keybase, cfg *broCfg) (gnoclient.Signer, error) { + var signer gnoclient.SignerFromKeybase + + signer.Keybase = kb + signer.Account = address + signer.ChainID = cfg.chainID + + if ok, err := kb.HasByNameOrAddress(address); !ok || err != nil { + if err != nil { + return nil, fmt.Errorf("invalid name: %w", err) + } + + return nil, fmt.Errorf("unknown name/address: %q", address) + } + + // try empty password first + if _, err := kb.ExportPrivKeyUnsafe(address, ""); err != nil { + prompt := fmt.Sprintf("[%.10s] Enter password:", address) + signer.Password, err = io.GetPassword(prompt, true) + if err != nil { + return nil, fmt.Errorf("error while reading password: %w", err) + } + + if _, err := kb.ExportPrivKeyUnsafe(address, signer.Password); err != nil { + return nil, fmt.Errorf("invalid password: %w", err) + } + } + + return signer, nil +} + +func ValidatePathCommandMiddleware(pathPrefix string) wish.Middleware { + return func(next ssh.Handler) ssh.Handler { + return func(s ssh.Session) { + switch cmd := s.Command(); len(cmd) { + case 0: // ok + next(s) + return + case 1: // check for valid path + path := cmd[0] + if strings.HasPrefix(path, pathPrefix) && filepath.Clean(path) == path { + s.Context().SetValue("path", path) + next(s) + return + } + + fmt.Fprintln(s.Stderr(), "provided path is invalid") + default: + fmt.Fprintln(s.Stderr(), "too many arguments") + } + + s.Exit(1) + } + } +} + +func StructuredMiddlewareWithLogger(ctx context.Context, logger *slog.Logger, level slog.Level) wish.Middleware { + return func(next ssh.Handler) ssh.Handler { + return func(sess ssh.Session) { + ct := time.Now() + hpk := sess.PublicKey() != nil + pty, _, _ := sess.Pty() + logger.Log( + ctx, + level, + "connect", + "user", sess.User(), + "remote-addr", sess.RemoteAddr().String(), + "public-key", hpk, + "command", sess.Command(), + "term", pty.Term, + "width", pty.Window.Width, + "height", pty.Window.Height, + "client-version", sess.Context().ClientVersion(), + ) + next(sess) + logger.Log( + ctx, + level, + "disconnect", + "user", sess.User(), + "remote-addr", sess.RemoteAddr().String(), + "duration", time.Since(ct), + ) + } + } +} + +func newLogger(out io.Writer, json bool) *slog.Logger { + if json { + return slog.New(slog.NewJSONHandler(out, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) + } + + charmlogger := charmlog.New(out) + charmlogger.SetLevel(charmlog.DebugLevel) + return slog.New(charmlogger) +} diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go index b263cc44f70..95c2c3efffc 100644 --- a/contribs/gnodev/cmd/gnodev/accounts.go +++ b/contribs/gnodev/cmd/gnodev/accounts.go @@ -10,6 +10,7 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/address" "github.com/gnolang/gno/contribs/gnodev/pkg/dev" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/std" @@ -50,7 +51,7 @@ func (va varPremineAccounts) String() string { func generateBalances(bk *address.Book, cfg *devCfg) (gnoland.Balances, error) { bls := gnoland.NewBalances() - premineBalance := std.Coins{std.NewCoin("ugnot", 10e12)} + premineBalance := std.Coins{std.NewCoin(ugnot.Denom, 10e12)} entries := bk.List() diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 77631a0190f..f4859889a16 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -2,32 +2,54 @@ module github.com/gnolang/gno/contribs/gnodev go 1.22 -toolchain go1.22.4 - replace github.com/gnolang/gno => ../.. require ( - github.com/charmbracelet/lipgloss v0.9.1 - github.com/charmbracelet/log v0.3.1 + github.com/charmbracelet/bubbles v0.18.0 + github.com/charmbracelet/bubbletea v0.26.6 + github.com/charmbracelet/glamour v0.7.0 + github.com/charmbracelet/lipgloss v0.11.0 + github.com/charmbracelet/log v0.4.0 + github.com/charmbracelet/ssh v0.0.0-20240604154955-a40c6a0d028f + github.com/charmbracelet/wish v1.4.0 github.com/fsnotify/fsnotify v1.7.0 github.com/gnolang/gno v0.0.0-00010101000000-000000000000 - github.com/gorilla/websocket v1.5.1 + github.com/gorilla/websocket v1.5.3 + github.com/lrstanley/bubblezone v0.0.0-20240624011428-67235275f80c + github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 + github.com/sahilm/fuzzy v0.1.1 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 - golang.org/x/term v0.22.0 + golang.org/x/sync v0.8.0 + golang.org/x/term v0.23.0 ) require ( - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/alecthomas/chroma/v2 v2.8.0 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/charmbracelet/keygen v0.5.0 // indirect + github.com/charmbracelet/x/ansi v0.1.2 // indirect + github.com/charmbracelet/x/conpty v0.1.0 // indirect + github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // indirect + github.com/charmbracelet/x/input v0.1.2 // indirect + github.com/charmbracelet/x/term v0.1.1 // indirect + github.com/charmbracelet/x/termios v0.1.0 // indirect + github.com/charmbracelet/x/windows v0.1.2 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // 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.2 // indirect @@ -35,47 +57,55 @@ require ( 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/css v1.0.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.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.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 - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/muesli/reflow v0.3.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/microcosm-cc/bluemonday v1.0.25 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.11.0 // indirect - github.com/rs/xid v1.5.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yuin/goldmark v1.5.4 // indirect + github.com/yuin/goldmark-emoji v1.0.2 // 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.etcd.io/bbolt v1.3.11 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.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 + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index f4eb9423c21..af57f320257 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -1,20 +1,34 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264= +github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= +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/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 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.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= 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.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= -github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/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.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= 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.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -30,14 +44,42 @@ 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/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= -github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= +github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s= +github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk= +github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= +github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= +github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc= +github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8= +github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= +github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/charmbracelet/ssh v0.0.0-20240604154955-a40c6a0d028f h1:DnNHMcvpjh51pFVuYCxf+pVNdfZ3w51gGAtDiuVmFEk= +github.com/charmbracelet/ssh v0.0.0-20240604154955-a40c6a0d028f/go.mod h1:LmMZag2g7ILMmWtDmU7dIlctUopwmb73KpPzj0ip1uk= +github.com/charmbracelet/wish v1.4.0 h1:pL1uVP/YuYgJheHEj98teZ/n6pMYnmlZq/fcHvomrfc= +github.com/charmbracelet/wish v1.4.0/go.mod h1:ew4/MjJVfW/akEO9KmrQHQv1F7bQRGscRMrA+KtovTk= +github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= +github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= +github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/input v0.1.2 h1:QJAZr33eOhDowkkEQ24rsJy4Llxlm+fRDf/cQrmqJa0= +github.com/charmbracelet/x/input v0.1.2/go.mod h1:LGBim0maUY4Pitjn/4fHnuXb4KirU3DODsyuHuXdOyA= +github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= +github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= +github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k= +github.com/charmbracelet/x/termios v0.1.0/go.mod h1:H/EVv/KRnrYjz+fCYa9bsKdqF3S8ouDK0AZEbG7r+/U= +github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= +github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -49,6 +91,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC 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/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 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= @@ -85,6 +131,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ 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/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= @@ -93,12 +141,14 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.0/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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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= @@ -108,17 +158,30 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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/lrstanley/bubblezone v0.0.0-20240624011428-67235275f80c h1:hu82xYs8yOIM1TSq+L5VIZeRsHVROpe3gL0qscUlXJA= +github.com/lrstanley/bubblezone v0.0.0-20240624011428-67235275f80c/go.mod h1:fMHACHXouhQO+NLAFvHEeKdVSzG7L/O1khqsvswCTmk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= +github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= @@ -126,6 +189,8 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +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 v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -150,36 +215,50 @@ 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.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/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/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.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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= +github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= github.com/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.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -193,22 +272,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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/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/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/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= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.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= @@ -218,26 +297,27 @@ 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.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.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/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -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/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-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/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/contribs/gnodev/pkg/browser/client_dev.go b/contribs/gnodev/pkg/browser/client_dev.go new file mode 100644 index 00000000000..3d63b3abba7 --- /dev/null +++ b/contribs/gnodev/pkg/browser/client_dev.go @@ -0,0 +1,113 @@ +package browser + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/http" + "time" + + "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" + "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gorilla/websocket" +) + +const MaxBackoff = time.Second * 20 + +var ErrHandlerNotSet = errors.New("handler not set") + +type DevClient struct { + Logger *slog.Logger + Handler func(typ events.Type, data any) error + + conn *websocket.Conn +} + +func (c *DevClient) Run(ctx context.Context, addr string, header http.Header) error { + if c.Handler == nil { + return ErrHandlerNotSet + } + + if c.Logger == nil { + c.Logger = log.NewNoopLogger() + } + + for ctx.Err() == nil { + if err := c.dialBackoff(ctx, addr, nil); err != nil { + return err + } + + c.Logger.Info("connected to server", "addr", addr) + + err := c.handleEvents(ctx) + if err == nil { + return nil + } + + var closeError *websocket.CloseError + if errors.As(err, &closeError) { + c.Logger.Error("connection has been closed, reconnecting...", "err", closeError) + continue + } + + return fmt.Errorf("unexpected error: %w", err) + } + + return context.Cause(ctx) +} + +func (c *DevClient) dialBackoff(ctx context.Context, addr string, header http.Header) error { + dialer := websocket.DefaultDialer + backoff := time.Second + for { + var err error + + c.Logger.Debug("connecting to dev events endpoint", addr, "addr") + c.conn, _, err = dialer.DialContext(ctx, addr, header) + + if ctx.Err() != nil { + return context.Cause(ctx) + } + + if err == nil { + return nil + } + + switch { + case backoff > MaxBackoff: + backoff = MaxBackoff + case backoff < MaxBackoff: + backoff *= 2 + default: + } + + c.Logger.Info("could not connect to server", "err", err, "next_attempt", backoff) + select { + case <-ctx.Done(): + return context.Cause(ctx) + case <-time.After(backoff): + } + } +} + +func (c *DevClient) handleEvents(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + go func() { + <-ctx.Done() + c.conn.Close() + }() + + for { + var evt emitter.EventJSON + if err := c.conn.ReadJSON(&evt); err != nil { + return fmt.Errorf("unable to read json event: %w", err) + } + + if err := c.Handler(evt.Type, evt.Data); err != nil { + return fmt.Errorf("unable to handle event: %w", err) + } + } +} diff --git a/contribs/gnodev/pkg/browser/client_node.go b/contribs/gnodev/pkg/browser/client_node.go new file mode 100644 index 00000000000..2ffa26aa08f --- /dev/null +++ b/contribs/gnodev/pkg/browser/client_node.go @@ -0,0 +1,124 @@ +package browser + +import ( + "errors" + "fmt" + "log/slog" + "regexp" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/amino" +) + +var ( + ErrInternalError = errors.New("internal error") + ErrRenderNotFound = errors.New("render not found") +) + +type NodeClient struct { + base gnoclient.BaseTxCfg + client *gnoclient.Client + logger *slog.Logger +} + +func NewNodeClient(logger *slog.Logger, base gnoclient.BaseTxCfg, client *gnoclient.Client) *NodeClient { + return &NodeClient{ + base: base, + client: client, + logger: logger, + } +} + +func (ncl *NodeClient) Call(path, call string) ([]byte, error) { + method, args, err := parseMethodToArgs(call) + if err != nil { + return nil, fmt.Errorf("unable to parse method/args: %w", err) + } + + if len(args) == 0 { + args = nil + } + + infos, err := ncl.client.Signer.Info() + if err != nil { + return nil, fmt.Errorf("unable to get signer infos: %w", err) + } + + cm, err := ncl.client.Call(ncl.base, vm.MsgCall{ + Caller: infos.GetAddress(), + PkgPath: path, + Func: method, + Args: args, + }) + if err != nil { + return nil, err + } + + if cm.CheckTx.Error != nil { + return nil, fmt.Errorf("check error: %w", err) + } + + if cm.DeliverTx.Error != nil { + return nil, fmt.Errorf("delivry error: %w", err) + } + + return cm.DeliverTx.Data, nil +} + +func (ncl *NodeClient) Funcs(path string) (vm.FunctionSignatures, error) { + res, err := ncl.client.Query(gnoclient.QueryCfg{ + Path: "vm/qfuncs", + Data: []byte(path), + }) + if err != nil { + return nil, err + } + + if err := res.Response.Error; err != nil { + return nil, err + } + + var fsigs vm.FunctionSignatures + if err := amino.UnmarshalJSON(res.Response.Data, &fsigs); err != nil { + return nil, fmt.Errorf("unable to unmarshal response: %w", err) + } + + return fsigs, nil +} + +func (ncl *NodeClient) Render(path, args string) ([]byte, error) { + data, res, err := ncl.client.Render(path, args) + if err != nil { + return nil, err + } + if err := res.Response.Error; err != nil { + return nil, err + } + + return []byte(data), nil +} + +var reMethod = regexp.MustCompile(`([^(]+)\(([^)]*)\)`) + +func parseMethodToArgs(call string) (method string, args []string, err error) { + matches := reMethod.FindStringSubmatch(call) + if len(matches) == 0 { + return "", nil, fmt.Errorf("invalid call: %w", err) + } + + method = matches[1] + sargs := matches[2] + if sargs == "" { + return method, args, err + } + + // Splitting arguments by comma + args = strings.Split(sargs, ",") + for i, arg := range args { + args[i] = strings.Trim(strings.TrimSpace(arg), "\"") + } + + return method, args, err +} diff --git a/contribs/gnodev/pkg/browser/list_model.go b/contribs/gnodev/pkg/browser/list_model.go new file mode 100644 index 00000000000..db4656fbf24 --- /dev/null +++ b/contribs/gnodev/pkg/browser/list_model.go @@ -0,0 +1,154 @@ +package browser + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/sahilm/fuzzy" +) + +var ( + listItemStyle = lipgloss.NewStyle().PaddingLeft(4) + listSelectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + listPaginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) +) + +type FuncListModel struct { + list.Model + items []list.Item +} + +func (m *FuncListModel) SetItems(items []list.Item) { + m.Model.SetItems(items) + m.items = items +} + +func (m FuncListModel) Update(msg tea.Msg) (FuncListModel, tea.Cmd) { + var cmd tea.Cmd + m.Model, cmd = m.Model.Update(msg) + return m, cmd +} + +func (m *FuncListModel) OriginItems() []list.Item { + return m.items +} + +func (m *FuncListModel) Erase() { + m.Model.SetItems([]list.Item{}) +} + +func (m *FuncListModel) Reset() { + m.Model.SetItems(m.items) +} + +func (m *FuncListModel) FilterItems(pattern string) { + if pattern == "" { + m.Reset() + return + } + + i := strings.IndexRune(pattern, '(') + if i > 0 { + pattern = pattern[:i] + } + + data := make([]string, len(m.items)) + for i, item := range m.items { + data[i] = item.FilterValue() + } + + ranks := fuzzy.Find(pattern, data) + sort.Stable(ranks) + if len(ranks) > 0 && i > 0 { + m.Model.SetItems([]list.Item{m.items[ranks[0].Index]}) + return + } + + items := make([]list.Item, len(ranks)) + for i, r := range ranks { + items[i] = m.items[r.Index] + } + + m.Model.SetItems(items) +} + +type itemFunc vm.FunctionSignature + +func (i itemFunc) Name() string { return i.FuncName } +func (i itemFunc) Title() string { return i.Name() } +func (i itemFunc) Description() string { return i.FuncName } +func (i itemFunc) FilterValue() string { return i.FuncName } + +type itemFuncsDelegate struct{} + +func (d itemFuncsDelegate) Height() int { return 1 } +func (d itemFuncsDelegate) Spacing() int { return 0 } +func (d itemFuncsDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (d itemFuncsDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + fun, ok := listItem.(itemFunc) + if !ok { + return + } + + maxw := m.Width() - 10 + + var proto strings.Builder + fmt.Fprintf(&proto, "%s(", fun.FuncName) + for j, param := range fun.Params { + if j != 0 { + fmt.Fprint(&proto, ", ") + } + + fmt.Fprintf(&proto, "%s %s", param.Name, param.Type) + } + fmt.Fprint(&proto, ")") + + switch len(fun.Results) { + case 0: // none + case 1: + fmt.Fprintf(&proto, " %s", fun.Results[0].Type) + default: + fmt.Fprint(&proto, " (") + for j, res := range fun.Results { + if j != 0 { + fmt.Fprint(&proto, ", ") + } + + fmt.Fprint(&proto, res.Type) + } + fmt.Fprint(&proto, ")") + } + + fn := listItemStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return listSelectedItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + str := proto.String() + if len(str) > maxw { + str = str[:maxw-3] + "..." + } + + fmt.Fprint(w, fn(str)) +} + +func newFuncList() FuncListModel { + l := list.New([]list.Item{}, &itemFuncsDelegate{}, 0, 0) + l.SetShowStatusBar(false) + l.SetFilteringEnabled(true) + l.SetShowHelp(false) + l.SetShowPagination(false) + l.Styles.PaginationStyle = listPaginationStyle + return FuncListModel{ + Model: l, + items: l.Items(), + } +} diff --git a/contribs/gnodev/pkg/browser/model.go b/contribs/gnodev/pkg/browser/model.go new file mode 100644 index 00000000000..bdd7eac9c82 --- /dev/null +++ b/contribs/gnodev/pkg/browser/model.go @@ -0,0 +1,580 @@ +package browser + +import ( + "bytes" + clist "container/list" + "errors" + "fmt" + "log/slog" + "path/filepath" + "regexp" + "strings" + + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textinput" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + zone "github.com/lrstanley/bubblezone" + "github.com/muesli/reflow/wordwrap" + + "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/log" +) + +var promptStyle = func(r *lipgloss.Renderer) lipgloss.Style { + return r.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#dd7878")) +} + +var ErrEmptyRenderer = errors.New("empty rendrer") + +type Config struct { + URLPrefix string + URLDefaultValue string + Logger *slog.Logger + Renderer *lipgloss.Renderer + Readonly bool + Banner ModelBanner +} + +const DefaultGnoLandPrefix = "gno.land/" + +func DefaultConfig() Config { + return Config{ + Logger: log.NewNoopLogger(), + URLPrefix: DefaultGnoLandPrefix, + Renderer: lipgloss.DefaultRenderer(), + URLDefaultValue: "gnoland/home", + } +} + +type model struct { + render *lipgloss.Renderer + client *NodeClient + logger *slog.Logger + + // misc + banner ModelBanner + bannerDiscarded bool + + // Viewport + zone *zone.Manager + ready bool + viewport viewport.Model + height, width int + readonly bool + messageDisplay bool + + // Nav + taskLoader LoaderModel + + pageurls map[string]string + history *clist.List + current *clist.Element + + // Url + urlInput textinput.Model + urlPrefix string + + // Commands + listFuncs FuncListModel + commandInput textinput.Model + commandFocus bool +} + +func initURLInput(prefix string, r *lipgloss.Renderer) textinput.Model { + ti := textinput.New() + ti.Placeholder = "r/gnoland/blog" // XXX: Use as example, customize this ? + ti.Focus() + ti.CharLimit = 156 + ti.PromptStyle = promptStyle(r) + ti.Prompt = prefix + "/" + + return ti +} + +func initCommandInput(r *lipgloss.Renderer) textinput.Model { + ti := textinput.New() + ti.Placeholder = "" + ti.CharLimit = 156 + ti.PromptStyle = promptStyle(r) + ti.Prompt = "> " + + return ti +} + +func New(cfg Config, client *gnoclient.Client) tea.Model { + renderer := lipgloss.DefaultRenderer() + if cfg.Renderer != nil { + renderer = cfg.Renderer + } + + // Setup url input + urlinput := initURLInput(cfg.URLPrefix, renderer) + + path := cleanupRealmPath(cfg.URLPrefix, cfg.URLDefaultValue) + urlinput.SetValue(path) + + // Setup cmd input + cmdinput := initCommandInput(renderer) + + // XXX: Customize this + base := gnoclient.BaseTxCfg{ + GasFee: "1000000ugnot", + GasWanted: 2000000, + } + + nodeclient := NewNodeClient(cfg.Logger, base, client) + return &model{ + logger: cfg.Logger, + render: cfg.Renderer, + readonly: cfg.Readonly, + client: nodeclient, + taskLoader: newLoaderModel(), + + banner: cfg.Banner, + bannerDiscarded: cfg.Banner.Empty(), + + urlInput: urlinput, + urlPrefix: cfg.URLPrefix, + + commandInput: cmdinput, + listFuncs: newFuncList(), + + zone: zone.New(), + pageurls: map[string]string{}, + history: clist.New(), + } +} + +func (m model) Init() tea.Cmd { + m.history.Init() + return m.banner.Init() +} + +type fetchRealmMsg struct { + realmPath string +} + +func FetchRealm(path string) tea.Cmd { + return func() tea.Msg { return fetchRealmMsg{path} } +} + +func RefreshRealm() tea.Cmd { + return func() tea.Msg { return fetchRealmMsg{""} } +} + +type renderUpdateMsg struct { + Render []byte + Funcs vm.FunctionSignatures + Error error +} + +func (m *model) RenderUpdate(path string) tea.Cmd { + return func() tea.Msg { + var msg renderUpdateMsg + var err error + msg.Render, err = m.fetchRenderView(path) + if err != nil { + msg.Error = fmt.Errorf("unable to fetch view: %w", err) + return msg + } + + msg.Funcs, err = m.fetchFuncsList(path) + if err != nil { + msg.Error = fmt.Errorf("unable to fetch function list: %w", err) + return msg + } + + return msg + } +} + +type execCommandRequestMsg struct { + Path string + Command string +} + +func (m *model) ExecCommandRequest(path, command string) tea.Cmd { + return func() tea.Msg { + return execCommandRequestMsg{path, command} + } +} + +type execCommandMsg struct { + Response []byte + Error error +} + +func (m *model) ExecCommand(path, command string) tea.Cmd { + return func() tea.Msg { + res, err := m.client.Call(path, command) + return execCommandMsg{res, err} + } +} + +func (m *model) ExtendCommandInput() bool { + if !m.commandInput.Focused() { + return false + } + + if item, ok := m.listFuncs.SelectedItem().(itemFunc); ok { + var value string + if len(item.Params) > 0 { + value = item.Title() + "(" + } else { + value = item.Title() + "()" + } + + currentValue := m.commandInput.Value() + if len(value) > len(currentValue) && strings.HasPrefix(value, currentValue) { + m.commandInput.SetValue(value) + return true + } + + // Put cursor at the end + m.commandInput.CursorEnd() + } + + return false +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case fetchRealmMsg: + if msg.realmPath != "" { + return m, tea.Sequence(m.taskLoader.Add(1), m.moveToRealm(msg.realmPath)) + } + + // If no realm path is given simply refresh the current realm + path := m.getCurrentPath() + m.logger.Info("rendering realm", "path", path) + + return m, tea.Sequence(m.taskLoader.Add(1), m.RenderUpdate(path)) + + case execCommandRequestMsg: + m.logger.Info("requesting command", "path", msg.Path, "cmd", msg.Command) + cmd = m.ExecCommand(msg.Path, msg.Command) + return m, tea.Sequence(m.taskLoader.Add(1), cmd) + + case execCommandMsg: + m.taskLoader.Done() + + // If any error, display it as message. + if msg.Error != nil { + m.logger.Warn("command exec", "error", msg.Error) + + content := wordwrap.NewWriter(m.viewport.Width) + fmt.Fprint(content, msg.Error.Error()) + fmt.Fprintf(content, "\n\npress [enter] to dismiss error\n") + m.viewport.SetContent(content.String()) + m.messageDisplay = true + return m, nil + } + + // If any response, display it as message. + if res := bytes.TrimSpace(msg.Response); len(res) > 0 { + m.logger.Info("command exec", "res", string(res)) + + content := wordwrap.NewWriter(m.viewport.Width) + content.Write(res) + fmt.Fprintf(content, "\n\npress [enter] to dismiss message\n") + m.viewport.SetContent(content.String()) + m.messageDisplay = true + return m, nil + } + + // If no error or empty response is returned, simply refresh the page. + m.messageDisplay = false + return m, RefreshRealm() + + case renderUpdateMsg: + m.taskLoader.Done() + + var content string + if err := msg.Error; err != nil { + m.logger.Warn("render", "error", msg.Error) + // Write error to the frame + content = fmt.Sprintf("ERROR: %s", err.Error()) + } else { + content = string(m.findAndMarkURLs(msg.Render)) + } + + if len(msg.Funcs) > 0 { + items := make([]list.Item, 0, len(msg.Funcs)) + for _, fun := range msg.Funcs { + if fun.FuncName != "Render" { + items = append(items, itemFunc(fun)) + } + } + m.listFuncs.SetItems(items) + m.listFuncs.FilterItems(m.commandInput.Value()) + + // Update funcs list + m.listFuncs.Title = m.urlInput.Value() + m.listFuncs.SetSize(m.viewport.Width, 7) + } + + m.viewport.SetContent(content) + return m, cmd + + case SpinnerTickMsg: + if m.taskLoader.Active() { + m.taskLoader, cmd = m.taskLoader.Update(msg) + } + + case tea.MouseMsg: + cmd = m.updateMouse(msg) + + // Fallback on viewport + if cmd == nil { + m.viewport, cmd = m.viewport.Update(msg) + } + + return m, cmd + + case tea.KeyMsg: + cmd = m.updateKey(msg) + m.listFuncs.FilterItems(m.commandInput.Value()) + if !m.readonly && cmd == nil { + m.listFuncs, cmd = m.listFuncs.Update(msg) + } + + // Fallback on list funcs update + if cmd == nil { + m.viewport, cmd = m.viewport.Update(msg) + } + + // Fallback on viewport update + return m, cmd + + case tea.WindowSizeMsg: + m.width = msg.Width + + headerHeight := lipgloss.Height(m.headerView()) + footerHeight := lipgloss.Height(m.footerView()) + verticalMarginHeight := headerHeight + footerHeight + + if !m.ready { + m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) + m.viewport.YPosition = headerHeight + m.viewport.MouseWheelEnabled = true + m.viewport.MouseWheelDelta = 1 + m.ready = true + m.viewport.YPosition = headerHeight + 1 + + if value := m.urlInput.Value(); value != "" { + cmd = RefreshRealm() + m.updateHistory() + } + } else { + m.viewport.Width = msg.Width + m.viewport.Height = msg.Height - verticalMarginHeight + } + + m.height = m.viewport.Height + if !m.urlInput.Focused() && len(m.listFuncs.Items()) > 0 { + m.viewport.Height = m.height - lipgloss.Height(m.listFuncsView()) + } + + return m, cmd + } + + // Update other models + cmds := []tea.Cmd{cmd} + + if !m.bannerDiscarded { + var bannerCmd tea.Cmd + m.banner, bannerCmd = m.banner.Update(msg) + cmds = append(cmds, bannerCmd) + } + + var viewCmd tea.Cmd + m.viewport, viewCmd = m.viewport.Update(msg) + cmds = append(cmds, viewCmd) + + var funcCmd tea.Cmd + m.listFuncs, funcCmd = m.listFuncs.Update(msg) + cmds = append(cmds, funcCmd) + + return m, tea.Batch(cmds...) +} + +func (m *model) updateKey(msg tea.KeyMsg) tea.Cmd { + var cmd tea.Cmd + if !m.bannerDiscarded { + switch key := msg.String(); key { + case "ctrl+c": + return tea.Quit + case "enter": + m.bannerDiscarded = true + } + // Discard other input while banner is active + return nil + } + + switch msg.String() { + case "alt+down": + if m.urlInput.Focused() && !m.readonly { + m.urlInput.Blur() + cmd = m.commandInput.Focus() + m.commandFocus = true + } + case "alt+up": + if m.commandInput.Focused() { + m.commandInput.Blur() + cmd = m.urlInput.Focus() + m.commandFocus = false + } + case "tab": + if m.commandInput.Focused() { + m.ExtendCommandInput() + } + case "alt+r": + cmd = RefreshRealm() + case "enter": + // Update command on focus + if m.commandInput.Focused() && !m.messageDisplay { + if len(m.listFuncs.Items()) == 1 { + path := m.getCurrentPath() + cmd = m.ExecCommand(path, m.commandInput.Value()) + } else { + m.ExtendCommandInput() + } + + break + } + + // Update url on focus + if m.messageDisplay || m.urlInput.Focused() { + m.listFuncs.Erase() + + cmd = m.moveToRealm(m.urlInput.Value()) + if m.current.Value.(string) != m.urlInput.Value() { + m.updateHistory() + } + + // Discard message + m.messageDisplay = false + } + + case "ctrl+c", "esc": + return tea.Quit + default: + // handle url input + if m.urlInput.Focused() { + m.urlInput, cmd = m.urlInput.Update(msg) + } + + if m.commandInput.Focused() { + // handle command input + m.commandInput, cmd = m.commandInput.Update(msg) + } + } + + return cmd +} + +func (m *model) updateMouse(msg tea.MouseMsg) tea.Cmd { + if msg.Action != tea.MouseActionRelease { + return nil + } + + var cmd tea.Cmd + + switch { + case m.zone.Get("prev_button").InBounds(msg): + if path, ok := m.moveHistoryBackward(); ok { + cmd = m.moveToRealm(path) + } + case m.zone.Get("next_button").InBounds(msg): + if path, ok := m.moveHistoryForward(); ok { + cmd = m.moveToRealm(path) + } + case m.zone.Get("home_button").InBounds(msg): + if cmd = m.moveToRealm("gno.land/r/gnoland/home"); cmd != nil { + m.updateHistory() + } + + case m.zone.Get("url_input").InBounds(msg): + m.commandInput.Blur() + cmd = m.urlInput.Focus() + m.commandFocus = false + case !m.readonly && m.zone.Get("command_input").InBounds(msg): + m.urlInput.Blur() + cmd = m.commandInput.Focus() + m.commandFocus = true + default: + for mark := range m.pageurls { + if !m.zone.Get(mark).InBounds(msg) { + continue + } + + if uri := m.pageurls[mark]; uri != "" { + if cmd = m.moveToRealm(uri); cmd != nil { + m.updateHistory() + break + } + } + } + } + + return cmd +} + +// realm path surrounded by ansi escape sequences +var reUrlPattern = regexp.MustCompile(`(?mU)\x1b[^m]*m(?:(?:https?://)?gno.land)?(/[^\s]+)\x1b[^m]*m`) + +func (m model) findAndMarkURLs(body []byte) []byte { + var buf bytes.Buffer + lastIndex := 0 + + indexes := reUrlPattern.FindAllSubmatchIndex(body, -1) + for i, loc := range indexes { + match := string(body[loc[0]:loc[1]]) + uri := string(body[loc[2]:loc[3]]) + markid := fmt.Sprintf("url_%d", i) + + // Write bytes before match + buf.Write(body[lastIndex:loc[0]]) + + // Write quoted URL + buf.WriteString(m.zone.Mark(markid, match)) + m.pageurls[markid] = uri + lastIndex = loc[1] + } + // Write remaining bytes + buf.Write(body[lastIndex:]) + + // Cleanup previous urls + for i := len(indexes); i < len(m.pageurls); i++ { + markid := fmt.Sprintf("url_%d", i) + delete(m.pageurls, markid) + } + + return buf.Bytes() +} + +func (m model) fetchFuncsList(path string) (view vm.FunctionSignatures, err error) { + rlmpath, _, _ := strings.Cut(path, ":") + funcs, err := m.client.Funcs(rlmpath) + if err != nil { + return nil, fmt.Errorf("unable to fetch Render: %w", err) + } + + return funcs, nil +} + +func (m *model) getCurrentPath() string { + path := strings.Trim(m.urlInput.Value(), "/") + if len(path) == 0 { + return m.urlPrefix + } + + return filepath.Join(m.urlPrefix, path) +} diff --git a/contribs/gnodev/pkg/browser/model_banner.go b/contribs/gnodev/pkg/browser/model_banner.go new file mode 100644 index 00000000000..ac983951dab --- /dev/null +++ b/contribs/gnodev/pkg/browser/model_banner.go @@ -0,0 +1,99 @@ +package browser + +import ( + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" +) + +type ModelBanner struct { + Banner string + + offset int + frameIndex int + frames [][]string + + fps time.Duration +} + +func NewModelBanner(fps time.Duration, frames []string) ModelBanner { + splited := make([][]string, len(frames)) + for i, frame := range frames { + lines := strings.Split(frame, "\n") + for j, line := range lines { + lines[j] = line + "\033[0m" + } + splited[i] = lines + } + + return ModelBanner{ + frames: splited, + fps: fps, + } +} + +func (m ModelBanner) Empty() bool { + return m.frames == nil +} + +type ( + tickBannerMsg struct{} + tickBannerOffsetMsg struct{} +) + +func (m ModelBanner) tick() tea.Cmd { + return tea.Tick(m.fps, func(_ time.Time) tea.Msg { + return tickBannerMsg{} + }) +} + +func (m ModelBanner) tickOffset() tea.Cmd { + return tea.Tick(time.Second/10, func(_ time.Time) tea.Msg { + return tickBannerOffsetMsg{} + }) +} + +func (m ModelBanner) Init() tea.Cmd { + if m.Empty() { + return nil + } + + return tea.Batch(m.tickOffset(), m.tick()) +} + +func (m ModelBanner) Update(msg tea.Msg) (ModelBanner, tea.Cmd) { + var cmd tea.Cmd + switch msg.(type) { + case tickBannerOffsetMsg: + frame := m.frames[m.frameIndex] + m.Banner = getFrameLinesOffset(frame, m.offset) + if m.offset < (len(frame) / 2) { + m.offset++ + cmd = m.tickOffset() + } + + case tickBannerMsg: + frame := m.frames[m.frameIndex] + m.Banner = getFrameLinesOffset(frame, m.offset) + m.frameIndex = (m.frameIndex + 1) % len(m.frames) // move to next frame + cmd = m.tick() + // XXX: handle window size + } + return m, cmd +} + +func (m ModelBanner) View() string { + return m.Banner +} + +func getFrameLinesOffset(lines []string, offset int) string { + middle := len(lines) / 2 + if offset < middle { + start := middle - min(middle, offset) + end := middle + min(middle, offset) + lines = lines[start:end] + } + + return strings.Join(lines, "\n") +} diff --git a/contribs/gnodev/pkg/browser/model_nav.go b/contribs/gnodev/pkg/browser/model_nav.go new file mode 100644 index 00000000000..6936f413d94 --- /dev/null +++ b/contribs/gnodev/pkg/browser/model_nav.go @@ -0,0 +1,73 @@ +package browser + +import ( + "fmt" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" +) + +func (m *model) moveToRealm(realm string) tea.Cmd { + path := cleanupRealmPath(m.urlPrefix, realm) + + // Set uri input + m.urlInput.SetValue(path) + m.urlInput.CursorEnd() + + // return command update + return tea.Sequence(RefreshRealm(), m.urlInput.Focus()) +} + +func (m *model) updateHistory() { + v := m.urlInput.Value() + if m.history.Len() == 0 { + m.current = m.history.PushBack(v) + return + } + + m.current = m.history.InsertAfter(v, m.current) + for next := m.current.Next(); next != nil; { + m.history.Remove(next) + next = m.current.Next() + } +} + +func (m *model) moveHistoryForward() (string, bool) { + if next := m.current.Next(); next != nil { + m.current = next + return m.current.Value.(string), true + } + return "", false +} + +func (m *model) moveHistoryBackward() (string, bool) { + if prev := m.current.Prev(); prev != nil { + m.current = prev + return m.current.Value.(string), true + } + return "", false +} + +func (m model) fetchRenderView(path string) (view []byte, err error) { + rlmpath, args, _ := strings.Cut(path, ":") + res, err := m.client.Render(rlmpath, args) + if err != nil { + return nil, fmt.Errorf("unable to fetch Render: %w", err) + } + + r, err := glamour.NewTermRenderer( + glamour.WithStyles(CatppuccinStyleConfig), // XXX: use gno custom theme + glamour.WithWordWrap(m.viewport.Width), + ) + if err != nil { + return nil, fmt.Errorf("unable to get render view: %w", err) + } + + view, err = r.RenderBytes(res) + if err != nil { + return nil, fmt.Errorf("uanble to render markdown view: %w", err) + } + + return view, nil +} diff --git a/contribs/gnodev/pkg/browser/model_style.go b/contribs/gnodev/pkg/browser/model_style.go new file mode 100644 index 00000000000..f3c3a18fd96 --- /dev/null +++ b/contribs/gnodev/pkg/browser/model_style.go @@ -0,0 +1,239 @@ +package browser + +import "github.com/charmbracelet/glamour/ansi" + +const ( + defaultListIndent = 2 + defaultListLevelIndent = 4 + defaultMargin = 2 +) + +// Catpuccin style: https://github.com/catppuccin/catppuccin +// XXX: update this with `gno` colors scheme +var CatppuccinStyleConfig = ansi.StyleConfig{ + Document: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockPrefix: "\n", + BlockSuffix: "\n", + Color: stringPtr("#cad3f5"), + }, + Margin: uintPtr(defaultMargin), + }, + BlockQuote: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + Italic: boolPtr(true), + }, + Indent: uintPtr(1), + }, + List: ansi.StyleList{ + LevelIndent: defaultListIndent, + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + }, + }, + }, + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockSuffix: "\n", + Color: stringPtr("#cad3f5"), + Bold: boolPtr(true), + }, + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + BackgroundColor: stringPtr("#f0c6c6"), + Color: stringPtr("#181926"), + Bold: boolPtr(true), + }, + }, + H2: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "● ", + Color: stringPtr("#f5a97f"), + }, + }, + H3: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "◉ ", + Color: stringPtr("#eed49f"), + }, + }, + H4: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "○ ", + Color: stringPtr("#a6da95"), + }, + }, + H5: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "◌ ", + Color: stringPtr("#7dc4e4"), + }, + }, + H6: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "‣ ", + Color: stringPtr("#b7bdf8"), + }, + }, + Strikethrough: ansi.StylePrimitive{ + CrossedOut: boolPtr(true), + }, + Emph: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + Italic: boolPtr(true), + }, + Strong: ansi.StylePrimitive{ + Bold: boolPtr(true), + Color: stringPtr("#cad3f5"), + }, + HorizontalRule: ansi.StylePrimitive{ + Color: stringPtr("#6e738d"), + Format: "\n--------\n", + }, + Item: ansi.StylePrimitive{ + BlockPrefix: "• ", + }, + Enumeration: ansi.StylePrimitive{ + BlockPrefix: ". ", + Color: stringPtr("#cad3f5"), + }, + Task: ansi.StyleTask{ + StylePrimitive: ansi.StylePrimitive{}, + Ticked: "[✓] ", + Unticked: "[ ] ", + }, + Link: ansi.StylePrimitive{ + Color: stringPtr("#8aadf4"), + Underline: boolPtr(true), + }, + LinkText: ansi.StylePrimitive{ + Color: stringPtr("#b7bdf8"), + }, + Image: ansi.StylePrimitive{ + Color: stringPtr("#8aadf4"), + Underline: boolPtr(true), + }, + ImageText: ansi.StylePrimitive{ + Color: stringPtr("#b7bdf8"), + Format: "Image: {{.text}} →", + }, + Code: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#ee99a0"), + }, + }, + CodeBlock: ansi.StyleCodeBlock{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#1e2030"), + }, + Margin: uintPtr(defaultMargin), + }, + Chroma: &ansi.Chroma{ + Text: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + }, + Error: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + BackgroundColor: stringPtr("#ed8796"), + }, + Comment: ansi.StylePrimitive{ + Color: stringPtr("#6e738d"), + }, + CommentPreproc: ansi.StylePrimitive{ + Color: stringPtr("#8aadf4"), + }, + Keyword: ansi.StylePrimitive{ + Color: stringPtr("#c6a0f6"), + }, + KeywordReserved: ansi.StylePrimitive{ + Color: stringPtr("#c6a0f6"), + }, + KeywordNamespace: ansi.StylePrimitive{ + Color: stringPtr("#eed49f"), + }, + KeywordType: ansi.StylePrimitive{ + Color: stringPtr("#eed49f"), + }, + Operator: ansi.StylePrimitive{ + Color: stringPtr("#91d7e3"), + }, + Punctuation: ansi.StylePrimitive{ + Color: stringPtr("#939ab7"), + }, + Name: ansi.StylePrimitive{ + Color: stringPtr("#b7bdf8"), + }, + NameBuiltin: ansi.StylePrimitive{ + Color: stringPtr("#f5a97f"), + }, + NameTag: ansi.StylePrimitive{ + Color: stringPtr("#c6a0f6"), + }, + NameAttribute: ansi.StylePrimitive{ + Color: stringPtr("#eed49f"), + }, + NameClass: ansi.StylePrimitive{ + Color: stringPtr("#eed49f"), + }, + NameConstant: ansi.StylePrimitive{ + Color: stringPtr("#eed49f"), + }, + NameDecorator: ansi.StylePrimitive{ + Color: stringPtr("#f5bde6"), + }, + NameFunction: ansi.StylePrimitive{ + Color: stringPtr("#8aadf4"), + }, + LiteralNumber: ansi.StylePrimitive{ + Color: stringPtr("#f5a97f"), + }, + LiteralString: ansi.StylePrimitive{ + Color: stringPtr("#a6da95"), + }, + LiteralStringEscape: ansi.StylePrimitive{ + Color: stringPtr("#f5bde6"), + }, + GenericDeleted: ansi.StylePrimitive{ + Color: stringPtr("#ed8796"), + }, + GenericEmph: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + Italic: boolPtr(true), + }, + GenericInserted: ansi.StylePrimitive{ + Color: stringPtr("#a6da95"), + }, + GenericStrong: ansi.StylePrimitive{ + Color: stringPtr("#cad3f5"), + Bold: boolPtr(true), + }, + GenericSubheading: ansi.StylePrimitive{ + Color: stringPtr("#91d7e3"), + }, + Background: ansi.StylePrimitive{ + BackgroundColor: stringPtr("#1e2030"), + }, + }, + }, + Table: ansi.StyleTable{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{}, + }, + CenterSeparator: stringPtr("┼"), + ColumnSeparator: stringPtr("│"), + RowSeparator: stringPtr("─"), + }, + DefinitionDescription: ansi.StylePrimitive{ + BlockPrefix: "\n🠶 ", + }, +} + +func boolPtr(b bool) *bool { return &b } +func stringPtr(s string) *string { return &s } +func uintPtr(u uint) *uint { return &u } diff --git a/contribs/gnodev/pkg/browser/model_tasks.go b/contribs/gnodev/pkg/browser/model_tasks.go new file mode 100644 index 00000000000..553d0168b6d --- /dev/null +++ b/contribs/gnodev/pkg/browser/model_tasks.go @@ -0,0 +1,85 @@ +// modified version of ""github.com/charmbracelet/bubbles/spinner" + +package browser + +import ( + "time" + + tea "github.com/charmbracelet/bubbletea" +) + +type Spinner struct { + Frames []string + FPS time.Duration +} + +// TickMsg indicates that the timer has ticked and we should render a frame. +type SpinnerTickMsg time.Time + +var MeterLoader = Spinner{ + Frames: []string{ + "▱▱▱▱▱▱▱▱", "▰▱▱▱▱▱▱▱", "▰▰▱▱▱▱▱▱", "▰▰▰▱▱▱▱▱", + "▰▰▰▰▱▱▱▱", "▰▰▰▰▰▱▱▱", "▰▰▰▰▰▰▱▱", "▰▰▰▰▰▰▰▱", + "▰▰▰▰▰▰▰▰", "▱▰▰▰▰▰▰▰", "▱▱▰▰▰▰▰▰", "▱▱▱▰▰▰▰▰", + "▱▱▱▱▰▰▰▰", "▱▱▱▱▱▰▰▰", "▱▱▱▱▱▱▰▰", "▱▱▱▱▱▱▱▰", + }, + FPS: time.Second / 70, //nolint:gomnd +} + +type LoaderModel struct { + spinner Spinner + frame int + task int +} + +func newLoaderModel() LoaderModel { + return LoaderModel{ + spinner: MeterLoader, + } +} + +func (m LoaderModel) Update(msg tea.Msg) (LoaderModel, tea.Cmd) { + switch msg.(type) { + case SpinnerTickMsg: + m.frame = (m.frame + 1) % len(m.spinner.Frames) + return m, m.tick() + default: + return m, nil + } +} + +func (m LoaderModel) tick() tea.Cmd { + return tea.Tick(m.spinner.FPS, func(t time.Time) tea.Msg { + return SpinnerTickMsg(t) + }) +} + +func (m LoaderModel) Tick() tea.Msg { + return SpinnerTickMsg(time.Now()) +} + +func (m *LoaderModel) Active() bool { + return m.frame > 0 || m.task > 0 +} + +func (m *LoaderModel) Add(i int) tea.Cmd { + var cmd tea.Cmd + if i > 0 { + if m.task == 0 { + cmd = m.Tick + } + + m.task += i + } + return cmd +} + +func (m *LoaderModel) Done() { + if m.task > 0 { + m.task -= 1 + } +} + +func (m *LoaderModel) View() string { + return m.spinner.Frames[m.frame] +} diff --git a/contribs/gnodev/pkg/browser/model_view.go b/contribs/gnodev/pkg/browser/model_view.go new file mode 100644 index 00000000000..40d78d18c6d --- /dev/null +++ b/contribs/gnodev/pkg/browser/model_view.go @@ -0,0 +1,174 @@ +package browser + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/lipgloss" +) + +var ( + boxRoundedStyle = func(r *lipgloss.Renderer) lipgloss.Style { + b := lipgloss.RoundedBorder() + return r.NewStyle(). + BorderStyle(b). + Padding(0, 2) + } + + inputStyleLeft = func(r *lipgloss.Renderer) lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Right = "├" + return r.NewStyle(). + BorderStyle(b). + Padding(0, 2) + } + + infoStyle = func(r *lipgloss.Renderer) lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Left = "┤" + return boxRoundedStyle(r).Copy().BorderStyle(b) + } +) + +func (m model) View() string { + if !m.bannerDiscarded { + return m.bannerView() + } + + if !m.ready { + return "+" + } + + mainView := fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.bodyView(), m.footerView()) + return m.zone.Scan(mainView) +} + +func (m model) bannerView() string { + banner := m.banner.View() + if banner == "" || m.width == 0 || m.height == 0 { + return "" + } + + // XXX: Encapsulate banner to avoid banner glitches + bannerView := m.render.NewStyle().Margin(1). + Render(banner) + widthView := m.width + 1 + + return lipgloss.Place(widthView, m.height, lipgloss.Center, lipgloss.Center, + lipgloss.JoinVertical(lipgloss.Center, + bannerView, + "press to continue", + ), + ) +} + +func (m model) listFuncsView() string { + return boxRoundedStyle(m.render). + Render(m.listFuncs.View()) +} + +func (m model) bodyView() string { + if m.commandInput.Focused() { + // handle command input + if v := m.commandInput.Value(); v != "" { + m.listFuncs.FilterItems(v) + } else { + m.listFuncs.Reset() + } + + if len(m.listFuncs.Items()) > 0 { + m.viewport.Height = m.height - lipgloss.Height(m.listFuncsView()) + } else { + m.viewport.Height = m.height + } + } + + return m.viewport.View() +} + +var ( + loadingStyle = func(r *lipgloss.Renderer) lipgloss.Style { + return r.NewStyle(). + Foreground(lipgloss.Color("#dd7878")). + Bold(true) + } + + navStyleEnable = func(r *lipgloss.Renderer) lipgloss.Style { + return r.NewStyle(). + Foreground(lipgloss.Color("#fab387")) + } + + navStyleDisable = func(r *lipgloss.Renderer) lipgloss.Style { + return r.NewStyle(). + Foreground(lipgloss.Color("240")) + } +) + +func (m model) navView() string { + home := navStyleEnable(m.render).Padding(0, 1).Render("[Home]") + + var style lipgloss.Style + if m.current != nil && m.current.Prev() != nil { + style = navStyleEnable(m.render) + } else { + style = navStyleDisable(m.render) + } + prev := style.Margin(0, 1).Render("") + + title := m.render.NewStyle().Bold(true).Render("Gno.Land") + if m.taskLoader.Active() { + title = loadingStyle(m.render).Render(m.taskLoader.View()) + } + + spaceWidth := m.width / 3 // left middle and right + return lipgloss.JoinHorizontal(lipgloss.Left, + m.render.NewStyle().Width(spaceWidth).Padding(0, 1). + Render(lipgloss.JoinHorizontal(lipgloss.Left, + m.zone.Mark("prev_button", prev), + m.zone.Mark("next_button", next), + )), + m.render.PlaceHorizontal(spaceWidth, lipgloss.Center, title), + m.render.PlaceHorizontal(spaceWidth, lipgloss.Right, + m.zone.Mark("home_button", home)), + ) +} + +func (m model) headerView() string { + return lipgloss.JoinVertical(lipgloss.Left, m.navView(), m.urlView()) +} + +func (m model) urlView() string { + return m.zone.Mark("url_input", boxRoundedStyle(m.render). + Width(m.viewport.Width-2). + Render(m.urlInput.View())) +} + +func (m model) footerView() string { + info := infoStyle(m.render).Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) + + if m.readonly { + // On readonly, simply discard command input interface + line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info))) + return lipgloss.JoinHorizontal(lipgloss.Center, line, info) + } + + command := m.zone.Mark("command_input", inputStyleLeft(m.render). + Width(m.viewport.Width-lipgloss.Width(info)-5). + Render(m.commandInput.View())) + line := strings.Repeat("─", 3) + + powerline := lipgloss.JoinHorizontal(lipgloss.Center, command, line, info) + if m.commandFocus && len(m.listFuncs.Items()) > 0 { + suggestions := m.listFuncsView() + return lipgloss.JoinVertical(lipgloss.Left, suggestions, powerline) + } + + return powerline +} diff --git a/contribs/gnodev/pkg/browser/utils.go b/contribs/gnodev/pkg/browser/utils.go new file mode 100644 index 00000000000..b322bea552a --- /dev/null +++ b/contribs/gnodev/pkg/browser/utils.go @@ -0,0 +1,33 @@ +package browser + +import ( + "path/filepath" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoweb" +) + +func redirectWebPath(path string) string { + if alias, ok := gnoweb.Aliases[path]; ok { + return alias + } + + if redirect, ok := gnoweb.Redirects[path]; ok { + return redirect + } + + return path +} + +func cleanupRealmPath(prefix, realm string) string { + // Trim prefix + path := strings.TrimPrefix(realm, prefix) + // redirect if any well known path + path = redirectWebPath(path) + // trim any slash + path = strings.TrimPrefix(path, "/") + // clean up path + path = filepath.Clean(path) + + return path +} diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 7f0c266bf48..c3e70366fb2 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" "github.com/gnolang/gno/contribs/gnodev/pkg/events" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/amino" @@ -52,7 +53,7 @@ func DefaultNodeConfig(rootdir string) *NodeConfig { balances := []gnoland.Balance{ { Address: defaultDeployer, - Amount: std.Coins{std.NewCoin("ugnot", 10e12)}, + Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e12)}, }, } @@ -87,7 +88,7 @@ type Node struct { currentStateIndex int } -var DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) +var DefaultFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) { mpkgs, err := NewPackagesMap(cfg.PackagesPathList) @@ -468,7 +469,9 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState) // Setup node config nodeConfig := newNodeConfig(n.config.TMConfig, n.config.ChainID, genesis) - nodeConfig.GenesisTxHandler = n.genesisTxHandler + nodeConfig.GenesisTxResultHandler = n.genesisTxResultHandler + // Speed up stdlib loading after first start (saves about 2-3 seconds on each reload). + nodeConfig.CacheStdlibLoad = true nodeConfig.Genesis.ConsensusParams.Block.MaxGas = n.config.MaxGasPerBlock // recoverFromError handles panics and converts them to errors. @@ -511,7 +514,7 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState) return nil } -func (n *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { +func (n *Node) genesisTxResultHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { if !res.IsErr() { return } diff --git a/contribs/gnodev/pkg/dev/node_state_test.go b/contribs/gnodev/pkg/dev/node_state_test.go index 17f96367512..efaeb979693 100644 --- a/contribs/gnodev/pkg/dev/node_state_test.go +++ b/contribs/gnodev/pkg/dev/node_state_test.go @@ -8,8 +8,8 @@ import ( 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/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -87,10 +87,10 @@ func TestSaveCurrentState(t *testing.T) { require.NoError(t, err) // Send a new tx - msg := gnoclient.MsgCall{ - PkgPath: testCounterRealm, - FuncName: "Inc", - Args: []string{"10"}, + msg := vm.MsgCall{ + PkgPath: testCounterRealm, + Func: "Inc", + Args: []string{"10"}, } res, err := testingCallRealm(t, node, msg) @@ -169,10 +169,10 @@ func Render(_ string) string { return strconv.Itoa(value) } for i := 0; i < inc; i++ { t.Logf("call %d", i) // Craft `Inc` msg - msg := gnoclient.MsgCall{ - PkgPath: testCounterRealm, - FuncName: "Inc", - Args: []string{"1"}, + msg := vm.MsgCall{ + PkgPath: testCounterRealm, + Func: "Inc", + Args: []string{"1"}, } res, err := testingCallRealm(t, node, msg) diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index 48204b4ce8d..11b0a2090d7 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -10,7 +10,9 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/events" "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -189,11 +191,11 @@ func Render(_ string) string { return str } require.Equal(t, render, "foo") // Call `UpdateStr` to update `str` value with "bar" - msg := gnoclient.MsgCall{ - PkgPath: "gno.land/r/dev/foo", - FuncName: "UpdateStr", - Args: []string{"bar"}, - Send: "", + msg := vm.MsgCall{ + PkgPath: "gno.land/r/dev/foo", + Func: "UpdateStr", + Args: []string{"bar"}, + Send: nil, } res, err := testingCallRealm(t, node, msg) require.NoError(t, err) @@ -236,7 +238,7 @@ func testingRenderRealm(t *testing.T, node *Node, rlmpath string) (string, error return render, err } -func testingCallRealm(t *testing.T, node *Node, msgs ...gnoclient.MsgCall) (*core_types.ResultBroadcastTxCommit, error) { +func testingCallRealm(t *testing.T, node *Node, msgs ...vm.MsgCall) (*core_types.ResultBroadcastTxCommit, error) { t.Helper() signer := newInMemorySigner(t, node.Config().ChainID()) @@ -246,11 +248,19 @@ func testingCallRealm(t *testing.T, node *Node, msgs ...gnoclient.MsgCall) (*cor } txcfg := gnoclient.BaseTxCfg{ - GasFee: "1000000ugnot", // Gas fee - GasWanted: 2_000_000, // Gas wanted + GasFee: ugnot.ValueString(1000000), // Gas fee + GasWanted: 2_000_000, // Gas wanted } - return cli.Call(txcfg, msgs...) + // Set Caller in the msgs + caller, err := signer.Info() + require.NoError(t, err) + vmMsgs := make([]vm.MsgCall, 0, len(msgs)) + for _, msg := range msgs { + vmMsgs = append(vmMsgs, vm.NewMsgCall(caller.GetAddress(), msg.Send, msg.PkgPath, msg.Func, msg.Args)) + } + + return cli.Call(txcfg, vmMsgs...) } func generateTestingPackage(t *testing.T, nameFile ...string) PackagePath { diff --git a/contribs/gnodev/pkg/dev/packages_test.go b/contribs/gnodev/pkg/dev/packages_test.go index 605db312429..151a89a7815 100644 --- a/contribs/gnodev/pkg/dev/packages_test.go +++ b/contribs/gnodev/pkg/dev/packages_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gnolang/gno/contribs/gnodev/pkg/address" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" @@ -26,33 +27,60 @@ func TestResolvePackagePathQuery(t *testing.T) { ExpectedPackagePath PackagePath ShouldFail bool }{ - {".", PackagePath{ + { Path: ".", - }, false}, - {"/simple/path", PackagePath{ + ExpectedPackagePath: PackagePath{ + Path: ".", + }, + }, + { Path: "/simple/path", - }, false}, - {"/ambiguo/u//s/path///", PackagePath{ - Path: "/ambiguo/u/s/path", - }, false}, - {"/path/with/creator?creator=testAccount", PackagePath{ - Path: "/path/with/creator", - Creator: testingAddress, - }, false}, - {"/path/with/deposit?deposit=100ugnot", PackagePath{ - Path: "/path/with/deposit", - Deposit: std.MustParseCoins("100ugnot"), - }, false}, - {".?creator=g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na&deposit=100ugnot", PackagePath{ - Path: ".", - Creator: testingAddress, - Deposit: std.MustParseCoins("100ugnot"), - }, false}, + ExpectedPackagePath: PackagePath{ + Path: "/simple/path", + }, + }, + { + Path: "/ambiguo/u//s/path///", + ExpectedPackagePath: PackagePath{ + Path: "/ambiguo/u/s/path", + }, + }, + { + Path: "/path/with/creator?creator=testAccount", + ExpectedPackagePath: PackagePath{ + Path: "/path/with/creator", + Creator: testingAddress, + }, + }, + { + Path: "/path/with/deposit?deposit=" + ugnot.ValueString(100), + ExpectedPackagePath: PackagePath{ + Path: "/path/with/deposit", + Deposit: std.MustParseCoins(ugnot.ValueString(100)), + }, + }, + { + Path: ".?creator=g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na&deposit=" + ugnot.ValueString(100), + ExpectedPackagePath: PackagePath{ + Path: ".", + Creator: testingAddress, + Deposit: std.MustParseCoins(ugnot.ValueString(100)), + }, + }, // errors cases - {"/invalid/account?creator=UnknownAccount", PackagePath{}, true}, - {"/invalid/address?creator=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", PackagePath{}, true}, - {"/invalid/deposit?deposit=abcd", PackagePath{}, true}, + { + Path: "/invalid/account?creator=UnknownAccount", + ShouldFail: true, + }, + { + Path: "/invalid/address?creator=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", + ShouldFail: true, + }, + { + Path: "/invalid/deposit?deposit=abcd", + ShouldFail: true, + }, } for _, tc := range cases { diff --git a/contribs/gnodev/pkg/emitter/server.go b/contribs/gnodev/pkg/emitter/server.go index e6052890095..3e32984268d 100644 --- a/contribs/gnodev/pkg/emitter/server.go +++ b/contribs/gnodev/pkg/emitter/server.go @@ -60,7 +60,7 @@ func (s *Server) Emit(evt events.Event) { go s.emit(evt) } -type eventJSON struct { +type EventJSON struct { Type events.Type `json:"type"` Data any `json:"data"` } @@ -69,7 +69,7 @@ func (s *Server) emit(evt events.Event) { s.muClients.Lock() defer s.muClients.Unlock() - jsonEvt := eventJSON{evt.Type(), evt} + jsonEvt := EventJSON{evt.Type(), evt} s.logger.Info("sending event to clients", "clients", len(s.clients), diff --git a/contribs/gnodev/pkg/emitter/server_test.go b/contribs/gnodev/pkg/emitter/server_test.go index 4725378dbda..8795d2da048 100644 --- a/contribs/gnodev/pkg/emitter/server_test.go +++ b/contribs/gnodev/pkg/emitter/server_test.go @@ -40,7 +40,7 @@ func TestServer_ServeHTTP(t *testing.T) { sendEvt := events.Custom("TEST") svr.Emit(sendEvt) // simulate reload - var recvEvt eventJSON + var recvEvt EventJSON err = c.ReadJSON(&recvEvt) require.NoError(t, err) assert.Equal(t, sendEvt.Type(), recvEvt.Type) diff --git a/contribs/gnofaucet/README.md b/contribs/gnofaucet/README.md new file mode 100644 index 00000000000..eefa41a8c6f --- /dev/null +++ b/contribs/gnofaucet/README.md @@ -0,0 +1,25 @@ +# Start a local faucet + +## Step1: + +Make sure you have started gnoland + + ../../gno.land/build/gnoland start -lazy + +## Step2: + +Start the faucet. + + ./build/gnofaucet serve -chain-id dev -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" + +By default, the faucet sends out 10,000,000ugnot (10gnot) per request. + +## Step3: + +Make sure you have started website + + ../../gno.land/build/gnoweb + +Request testing tokens from following URL, Have fun! + + http://localhost:8888/faucet \ No newline at end of file diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 0662de5f82f..c56c0b7d425 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -5,8 +5,8 @@ go 1.22 toolchain go1.22.4 require ( - github.com/gnolang/faucet v0.2.1 - github.com/gnolang/gno v0.1.0-nightly.20240627 + github.com/gnolang/faucet v0.3.2 + github.com/gnolang/gno v0.1.1 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/time v0.5.0 @@ -19,9 +19,10 @@ require ( 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-chi/chi/v5 v5.0.12 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-chi/chi/v5 v5.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // 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.20.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect @@ -29,27 +30,27 @@ require ( 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.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc 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.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.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.25.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/sync v0.7.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 + 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/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 29dc3efcb76..1508cdae1e6 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -45,17 +45,17 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 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/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.1.0-nightly.20240627 h1:ZfVzzr2nadX5NLsZ76WN2CLb7TTuMJMwCdqTBZXVLGo= -github.com/gnolang/gno v0.1.0-nightly.20240627/go.mod h1:WCOCLF55BgFd2cw/Rrqa4zk9nK4AVVvbrPKprhkG4Wo= +github.com/gnolang/faucet v0.3.2 h1:3QBrdmnQszRaAZbxgO5xDDm3czNa0L/RFmhnCkbxy5I= +github.com/gnolang/faucet v0.3.2/go.mod h1:/wbw9h4ooMzzyNBuM0X+ol7CiPH2OFjAFF3bYAXqA7U= +github.com/gnolang/gno v0.1.1 h1:t41S0SWIUa3syI7XpRAuCneCgRc8gOJ2g8DkUedF72U= +github.com/gnolang/gno v0.1.1/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/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 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= @@ -74,6 +74,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/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= @@ -119,22 +121,22 @@ 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.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/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.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= @@ -146,19 +148,19 @@ 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.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= -golang.org/x/mod v0.17.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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -171,34 +173,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.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/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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.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/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-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/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.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/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/go.mod b/contribs/gnokeykc/go.mod index 7cac81cfaaf..a8e235a5c5a 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -13,9 +13,10 @@ require ( require ( github.com/alessio/shellescape v1.4.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -27,35 +28,36 @@ require ( 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.20.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.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 github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/xid v1.5.0 // indirect + github.com/rs/xid v1.6.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.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/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/crypto v0.25.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.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 + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 136a8b55d55..b3bfadb3468 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -1,20 +1,22 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 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.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= 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.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= -github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/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.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= 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.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -30,6 +32,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= @@ -80,10 +84,10 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ 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.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/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= @@ -93,6 +97,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= @@ -118,12 +124,17 @@ 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/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/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.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.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= @@ -134,40 +145,42 @@ 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.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/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/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/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= @@ -178,23 +191,23 @@ 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.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/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-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/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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/docs/concepts/namespaces.md b/docs/concepts/namespaces.md index 74129697c88..0f9176bcbf1 100644 --- a/docs/concepts/namespaces.md +++ b/docs/concepts/namespaces.md @@ -4,13 +4,19 @@ id: namespaces # Namespaces -Namespaces provide users with the exclusive capability to publish contracts under their designated namespaces, similar to GitHub's user and organization model. +Namespaces provide users with the exclusive capability to publish contracts under their designated namespaces, +similar to GitHub's user and organization model. -This feature is currently a work in progress (WIP). To learn more about namespaces, please checkout https://github.com/gnolang/gno/issues/1107. +:::warning Not enabled + +This feature isn't enabled by default on the portal loop chain and is currently available only on test4.gno.land. + +::: # Package Path -A package path is a unique identifier for each package/realm. It specifies the location of the package source code which helps differentiate it from others. You can use a package path to: +A package path is a unique identifier for each package/realm. It specifies the location of the package source +code which helps differentiate it from others. You can use a package path to: - Call a specific function from a package/realm. (e.g using `gnokey maketx call`) - Import it in other packages/realms. @@ -21,7 +27,9 @@ Here's a breakdown of the structure of a package path: - Type: Defines the type of package. - `p/`: [Package](packages.md) - `r/`: [Realm](realms.md) -- Namespace: A namespace can be included after the type (e.g., user or organization name). Namespaces are a way to group related packages or realms, but currently ownership cannot be claimed. (see [Issue #1107](https://github.com/gnolang/gno/issues/1107) for more info) +- Namespace: A namespace can be included after the type (e.g., user or organization name). Namespaces are a + way to group related packages or realms, but currently ownership cannot be claimed. (see + [Issue#1107](https://github.com/gnolang/gno/issues/1107) for more info) - Remaining Path: The remaining part of the path. - Can only contain alphanumeric characters (letters and numbers) and underscores. - No special characters allowed (except underscore). @@ -33,3 +41,51 @@ Examples: - `gno.land/p/demo/avl`: This signifies a package named `avl` within the `demo` namespace. - `gno.land/r/gnoland/home`: This signifies a realm named `home` within the `gnoland` namespace. + +## Registration Process + +The registration process is contract-based. The `AddPkg` command references +`sys/users` for filtering, which in turn is based on `r/demo/users`. + +When `sys/users` is enabled, you need to register a name using `r/demo/users`. You can call the +`r/demo/users.Register` function to register the name for the caller's address. + +> ex: `test1` user registering as `patrick` +```bash +$ gnokey maketx call -pkgpath gno.land/r/demo/users \ + -func Register \ + -gas-fee 1000000ugnot -gas-wanted 2000000 \ + -broadcast \ + -chainid=test4 \ + -send=20000000ugnot \ + -args '' \ + -args 'patrick' \ + -args 'My Profile Quote' test1 +``` + +:::note Chain-ID + +Do not forget to update chain id, adequate to the network you're interacting with + +::: + + +After successful registration, you can add a package under the registered namespace. + +## Anonymous Namespace + +Gno.land offers the ability to add a package without having a registered namespace. +You can do this by using your own address as a namespace. This is formatted as `{p,r}/{std.Address}/**`. + +> ex: with `test1` user adding a package `microblog` using his own address as namespace +```bash +$ gnokey maketx addpkg \ + --pkgpath "gno.land/r/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/microblog" \ + --pkgdir "examples/gno.land/p/demo/microblog" \ + --deposit 100000000ugnot \ + --gas-fee 1000000ugnot \ + --gas-wanted 2000000 \ + --broadcast \ + --chainid test4 \ + test1 +``` diff --git a/docs/concepts/portal-loop.md b/docs/concepts/portal-loop.md index d96738bdddf..adc341e3ae4 100644 --- a/docs/concepts/portal-loop.md +++ b/docs/concepts/portal-loop.md @@ -67,3 +67,20 @@ has some drawbacks: Gno will fail to be replayed, meaning **data will be lost**. - Since transactions are archived and replayed during genesis, block height & timestamp cannot be relied upon. + +### Deploying to the Portal Loop + +There are two ways to deploy code to the Portal Loop: + +1. *automatic* - all packages in found in the `examples/gno.land/{p,r}/` directory in the [Gno monorepo](https://github.com/gnolang/gno) get added to the + new genesis each cycle, +2. *permissionless* - this includes replayed transactions with `addpkg`, and + new transactions you can issue with `gnokey maketx addpkg`. + +Since the packages in `examples/gno.land/{p,r}` are deployed first, +permissionless deployments get superseded when packages with identical `pkgpath` +get merged into `examples/`. + +The above mechanism is also how the `examples/` on the Portal Loop +get collaboratively iterated upon, which is its main mission. + diff --git a/docs/concepts/testnets.md b/docs/concepts/testnets.md index f9a1ea6e515..730795d3742 100644 --- a/docs/concepts/testnets.md +++ b/docs/concepts/testnets.md @@ -43,6 +43,21 @@ For more information on the Portal Loop, and how it can be best utilized, check out the [Portal Loop concept page](./portal-loop.md). Also, you can find the Portal Loop faucet on [`gno.land/faucet`](https://gno.land/faucet). +## Test4 +Test4 a permanent multi-node testnet. + +- **Persistence of state:** + - State is fully persisted unless there are breaking changes in a new release, + where persistence partly depends on implementing a migration strategy +- **Timeliness of code:** + - Versioning mechanisms for packages & realms will be implemented for test4 +- **Intended purpose** + - Running a full node, testing validator coordination, deploying stable Gno + dApps, creating tools that require persisted state & transaction history +- **Versioning strategy**: + - Test4 is the first gno.land testnet to be release-based, following releases +of the Gno tech stack. + ## Staging Staging is a testnet that is reset once every 60 minutes. @@ -57,24 +72,6 @@ Staging is a testnet that is reset once every 60 minutes. - **Versioning strategy**: - Staging is reset every 60 minutes to match the latest monorepo commit -## Test4 (upcoming) -Test4 (name subject to change) is an upcoming, permanent, multi-node testnet. -To follow test4 progress, view the test4 milestone -[here](https://github.com/gnolang/gno/milestone/4). -Once it is complete, it will have the following properties: - -- **Persistence of state:** - - State is fully persisted unless there are breaking changes in a new release, -where persistence partly depends on implementing a migration strategy -- **Timeliness of code:** - - Versioning mechanisms for packages & realms will be implemented for test4 -- **Intended purpose** - - Running a full node, testing validator coordination, deploying stable Gno -dApps, creating tools that require persisted state & transaction history -- **Versioning strategy**: - - Test4 will be the first testnet to be release-based, following releases of -the Gno tech stack. - ## TestX These testnets are deprecated and currently serve as archives of previous progress. diff --git a/docs/getting-started/local-setup/creating-a-keypair.md b/docs/getting-started/local-setup/creating-a-keypair.md new file mode 100644 index 00000000000..983d732a0fd --- /dev/null +++ b/docs/getting-started/local-setup/creating-a-keypair.md @@ -0,0 +1,77 @@ +--- +id: creating-a-keypair +--- + +# Creating a Keypair + +## Overview + +In this tutorial, you will learn how to create your Gno keypair using +[`gnokey`](../../gno-tooling/cli/gnokey/gnokey.md). + +Keypairs are the foundation of how users interact with blockchains; and Gno is +no exception. By using a 12-word or 24-word [mnemonic phrase](https://www.zimperium.com/glossary/mnemonic-seed/) +as a source of randomness, users can derive a private and a public key. +These two keys can then be used further; a public key derives an address which is +a unique identifier of a user on the blockchain, while a private key is used for +signing messages and transactions for the aforementioned address, proving a user +has ownership over it. + +Let's see how we can use `gnokey` to generate a Gno keypair locally. + +## Generating a keypair + +The `gnokey add` command allows you to generate a new keypair locally. Simply +run the command, while adding a name for your keypair: + +```bash +gnokey add MyKey +``` + +![gnokey-add-random](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif) + +After running the command, `gnokey` will ask you to enter a password that will be +used to encrypt your keypair to the disk. Then, it will show you the following +information: +- Your public key, as well as the Gno address derived from it, starting with `g1...`, +- Your randomly generated 12-word mnemonic phrase which was used to derive the keypair. + +:::warning Safeguard your mnemonic phrase! + +A **mnemonic phrase** is like your master password; you can use it over and over +to derive the same keypairs. This is why it is crucial to store it in a safe, +offline place - writing the phrase on a piece of paper and hiding it is highly +recommended. **If it gets lost, it is unrecoverable.** + +::: + +`gnokey` will generate a keybase in which it will store information about your +keypairs. The keybase directory path is stored under the `-home` flag in `gnokey`. + +### Gno addresses + +Your **Gno address** is like your unique identifier on the network; an address +is visible in the caller stack of an application, it is included in each +transaction you create with your keypair, and anyone who knows your address can +send you [coins](../../concepts/stdlibs/coin.md), etc. + +## Conclusion + +That's it 🎉 + +You've successfully created your first Gno keypair. Check out +[Browsing gno.land](./browsing-gnoland.md) and +[Interacting with gno.land](./interacting-with-gnoland.md) to see how you can +use it. + +If you wish to learn more about `gnokey` specifically, check out the +[gnokey section](../../gno-tooling/cli/gnokey/gnokey.md). + + + + + + + + + diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index 58f71f93026..272d0069ee5 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -35,7 +35,7 @@ git clone https://github.com/gnolang/gno.git There are three tools that should be used for getting started with Gno development: - `gno` - the GnoVM binary - `gnodev` - the Gno [development helper](../../gno-tooling/cli/gnodev.md) -- `gnokey` - the Gno [keypair manager](working-with-key-pairs.md) +- `gnokey` - the Gno [keypair manager](../../gno-tooling/cli/gnokey/working-with-key-pairs.md) To install all three tools, simply run the following in the root of the repo: ```bash @@ -87,7 +87,7 @@ You should get the following output: `gnokey` is the gno.land keypair management CLI tool. It allows you to create keypairs, sign transactions, and broadcast them to gno.land chains. Read more -about `gnokey` [here](../../gno-tooling/cli/gnokey.md). +about `gnokey` [here](../../gno-tooling/cli/gnokey/gnokey.md). To verify that the `gnokey` binary is installed system-wide, you can run: @@ -106,5 +106,5 @@ That's it 🎉 You have successfully built out and installed the necessary tools for Gno development! -In further documents, you will gain a better understanding on how they are used +In further documents, you will gain a better understanding of how they are used to make Gno work. diff --git a/docs/getting-started/local-setup/interacting-with-gnoland.md b/docs/getting-started/local-setup/interacting-with-gnoland.md index e07c839d691..6b4b8213228 100644 --- a/docs/getting-started/local-setup/interacting-with-gnoland.md +++ b/docs/getting-started/local-setup/interacting-with-gnoland.md @@ -10,10 +10,10 @@ You will understand how to use your keypair to send transactions to realms and packages, send native coins, and more. ## Prerequisites + - **`gnokey` installed.** Reference the -[Local Setup](installation.md#3-installing-other-gno-tools) guide for steps -- **A keypair in `gnokey`.** Reference the -[Working with Key Pairs](working-with-key-pairs.md#adding-a-private-key-using-a-mnemonic) guide for steps +[Local Setup](installation.md) guide for steps +- **A keypair in `gnokey`.** Reference the [Creating a key pair](creating-a-keypair.md) guide for steps ## 1. Get testnet GNOTs For interacting with any gno.land chain, you will need a certain amount of GNOTs @@ -21,8 +21,7 @@ to pay gas fees with. For this example, we will use the [Portal Loop](../../concepts/testnets.md#portal-loop) testnet. We can access the Portal Loop faucet through the -[Gno Faucet Hub](https://faucet.gno.land), or by accessing the faucet directly at -[gno.land/faucet](https://gno.land/faucet). +[Gno Faucet Hub](https://faucet.gno.land). ![faucet-hub](../../assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub.png) @@ -35,7 +34,7 @@ After inputting your address and solving the captcha, you can check if you have following `gnokey` command: ```bash -gnokey query bank/balances/ --remote "https://rpc.gno.land:443" +gnokey query bank/balances/ --remote "https://rpc.gno.land:443" ``` If the faucet request was successful, you should see something similar to the @@ -48,6 +47,7 @@ data: "10000000ugnot" ``` ## 2. Visit a realm + For this example, we will use the [Userbook realm](https://gno.land/r/demo/userbook). The Userbook realm is a simple app that allows users to sign up, and keeps track of when they signed up. It also displays the currently signed-up users and the block @@ -55,8 +55,8 @@ height at which they have signed up. ![userbook-default](../../assets/getting-started/local-setup/interacting-with-gnoland/userbook-default.png) -> Note: block heights are not correct because of the way the Portal Loop testnet -> works. +> Note: block heights in this case are unreliable because of the way the Portal Loop +> network works. > Read more [here](../../concepts/portal-loop.md). To see what functions are available to call on the Userbook realm, click @@ -67,7 +67,7 @@ the `[help]` button. By choosing one of the two `gnokey` commands and inputting your address (or keypair name) in the top bar, you will have a ready command to paste into your terminal. For example, the following command will call the `SignUp` function with the -keypair `MyKeypair`: +keypair `MyKey`: ``` gnokey maketx call \ @@ -79,11 +79,11 @@ gnokey maketx call \ -broadcast \ -chainid "portal-loop" \ -remote "https://rpc.gno.land:443" \ -MyKeypair +MyKey ``` -To see what each option and flag in this command does, read the `gnokey` -[reference page](../../gno-tooling/cli/gnokey.md). +To see what each option and flag in this command does, check out `gnokey` in the +[tooling section](../../gno-tooling/cli/gnokey/gnokey.md). ## Conclusion @@ -92,6 +92,6 @@ That's it! Congratulations on executing your first transaction on a Gno network! If the previous transaction was successful, you should be able to see your address on the main page of the Userbook realm. -This concludes the "Local Setup" tutorial. For next steps, see the +This concludes the "Local Setup" section. For next steps, see the [How-to guides section](../../how-to-guides/how-to-guides.md), where you will learn how to write your first realm, package, and much more. diff --git a/docs/gno-infrastructure/validators/overview.md b/docs/gno-infrastructure/validators/overview.md index 7128a8c6b75..918bd218f50 100644 --- a/docs/gno-infrastructure/validators/overview.md +++ b/docs/gno-infrastructure/validators/overview.md @@ -7,11 +7,11 @@ id: validators-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), +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 +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 @@ -79,7 +79,7 @@ Join the official gno.land community in various channels to receive the latest u communicate with other validators and contributors. - [Gno.land Blog](https://gno.land/r/gnoland/blog) -- [Gno.land Discord](https://discord.gg/w2MpVEunxr) +- [Gno.land Discord](https://discord.gg/YFtMjWwUN7) - [Gno.land Twitter](https://x.com/_gnoland) :::info @@ -91,7 +91,7 @@ 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 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/setting-up-a-new-chain.md b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md index 5d440a86684..0411fa3b02a 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -93,7 +93,7 @@ Let's break down the most important default settings: :::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 +to delete this directory and `genesis.json`, then start the node up again. If you are using the default node configuration, you can run `make fclean` from the `gno.land` sub-folder to delete the `gnoland-data` working directory. ::: diff --git a/docs/gno-tooling/cli/faucet/faucet.md b/docs/gno-tooling/cli/faucet/faucet.md index 4d32f86e9ef..b069a19740a 100644 --- a/docs/gno-tooling/cli/faucet/faucet.md +++ b/docs/gno-tooling/cli/faucet/faucet.md @@ -22,7 +22,7 @@ The Gno faucet works by designating a single address as a faucet address that wi 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. +the [Working with Key Pairs](../gnokey/working-with-key-pairs.md) guide. ## 2. Start the local chain diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md index 4a1880822fc..f9491fea803 100644 --- a/docs/gno-tooling/cli/gnodev.md +++ b/docs/gno-tooling/cli/gnodev.md @@ -105,7 +105,7 @@ A specific deposit amount can also be set with the following pattern: gnodev ./myrealm?deposit=42ugnot ``` -This patten can be expanded to accommodate both options: +This pattern can be expanded to accommodate both options: ``` gnodev ./myrealm?creator=&deposit= diff --git a/docs/gno-tooling/cli/gnokey.md b/docs/gno-tooling/cli/gnokey.md deleted file mode 100644 index bf110faec5f..00000000000 --- a/docs/gno-tooling/cli/gnokey.md +++ /dev/null @@ -1,323 +0,0 @@ ---- -id: gno-tooling-gnokey ---- - -# gnokey - -Used for account & key management and general interactions with the Gnoland blockchain. - -## Generate a New Seed Phrase - -Generate a new seed phrase and add it to your keybase with the following command. - -```bash -gnokey generate -``` - -## Add a New Key - -You can add a new private key to the keybase using the following command. - -```bash -gnokey add {KEY_NAME} -``` - -#### **Options** - -| Name | Type | Description | -|-------------|------------|----------------------------------------------------------------------------------------| -| `account` | UInt | Account number for HD derivation. | -| `dryrun` | Boolean | Performs action, but doesn't add key to local keystore. | -| `index` | UInt | Address index number for HD derivation. | -| `ledger` | Boolean | Stores a local reference to a private key on a Ledger device. | -| `multisig` | String \[] | Constructs and stores a multisig public key (implies `--pubkey`). | -| `nobackup` | Boolean | Doesn't print out seed phrase (if others are watching the terminal). | -| `nosort` | Boolean | Keys passed to `--multisig` are taken in the order they're supplied. | -| `pubkey` | String | Parses a public key in bech32 format and save it to disk. | -| `recover` | Boolean | Provides seed phrase to recover existing key instead of creating. | -| `threshold` | Int | K out of N required signatures. For use in conjunction with --multisig (default: `1`). | - -> **Test Seed Phrase:** source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast - -### Using a ledger device - -You can add a ledger device using the following command - -> [!NOTE] -> Before running this command make sure your ledger device is connected, with the cosmos app installed and open in it. - -```bash -gnokey add {LEDGER_KEY_NAME} --ledger -``` - -## List all Known Keys - -List all keys stored in your keybase with the following command. - -```bash -gnokey list -``` - -## Delete a Key - -Delete a key from your keybase with the following command. - -```bash -gnokey delete {KEY_NAME} -``` - -#### **Options** - -| Name | Type | Description | -|---------|---------|------------------------------| -| `yes` | Boolean | Skips confirmation prompt. | -| `force` | Boolean | Removes key unconditionally. | - - -## Export a Private Key (Encrypted & Unencrypted) - -Export a private key's (encrypted or unencrypted) armor using the following command. - -```bash -gnokey export -``` - -#### **Options** - -| Name | Type | Description | -|---------------|--------|---------------------------------------------| -| `key` | String | Name or Bech32 address of the private key | -| `output-path` | String | The desired output path for the armor file | -| `unsafe` | Bool | Export the private key armor as unencrypted | - - -## Import a Private Key (Encrypted & Unencrypted) - -Import a private key's (encrypted or unencrypted) armor with the following command. - -```bash -gnokey import -``` - -#### **Options** - -| Name | Type | Description | -|--------------|--------|---------------------------------------------| -| `armor-path` | String | The path to the encrypted armor file. | -| `name` | String | The name of the private key. | -| `unsafe` | Bool | Import the private key armor as unencrypted | - - -## Make an ABCI Query - -Make an ABCI Query with the following command. - -```bash -gnokey query {QUERY_PATH} -``` - -#### **Query** - -| Query Path | Description | Example | -|---------------------------|--------------------------------------------------------------------|----------------------------------------------------------------------------------------| -| `auth/accounts/{ADDRESS}` | Returns information about an account. | `gnokey query auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` | -| `bank/balances/{ADDRESS}` | Returns balances of an account. | `gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` | -| `vm/qfuncs` | Returns public facing function signatures as JSON. | `gnokey query vm/qfuncs --data "gno.land/r/demo/boards"` | -| `vm/qfile` | Returns the file bytes, or list of files if directory. | `gnokey query vm/qfile --data "gno.land/r/demo/boards"` | -| `vm/qrender` | Calls .Render(path) in readonly mode. | `gnokey query vm/qrender --data "gno.land/r/demo/boards:"` | -| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | `gnokey query vm/qeval --data "gno.land/r/demo/boards.GetBoardIDFromName("my_board")"` | -| `vm/store` | (not yet supported) Fetches items from the store. | - | -| `vm/package` | (not yet supported) Fetches a package's files. | - | - -#### **Options** - -| Name | Type | Description | -|----------|-----------|------------------------------------------| -| `data` | UInt8 \[] | Queries data bytes. | - - -## Sign and Broadcast a Transaction - -You can sign and broadcast a transaction with the following command. - -```bash -gnokey maketx {SUB_COMMAND} {ADDRESS or KeyName} -``` - -#### **Subcommands** - -| Name | Description | -|----------|------------------------------| -| `addpkg` | Uploads a new package. | -| `call` | Calls a public function. | -| `send` | The amount of coins to send. | - -### `addpkg` - -This subcommand lets you upload a new package. - -```bash -gnokey maketx addpkg \ - -deposit="1ugnot" \ - -gas-fee="1ugnot" \ - -gas-wanted="5000000" \ - -pkgpath={Registered Realm path} \ - -pkgdir={Package folder path} \ - {ADDRESS} \ - > unsigned.tx -``` - -#### **SignBroadcast Options** - -| Name | Type | Description | -|--------------|---------|----------------------------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | 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] | - -#### **makeTx AddPackage Options** - -| Name | Type | Description | -|-----------|--------|---------------------------------------| -| `pkgpath` | String | The package path (required). | -| `pkgdir` | String | The path to package files (required). | -| `deposit` | String | The amount of coins to send. | - -### `call` - -This subcommand lets you call any exported function. - -```bash -# Register -gnokey maketx call \ - -gas-fee="1ugnot" \ - -gas-wanted="5000000" \ - -pkgpath="gno.land/r/demo/users" \ - -send="200000000ugnot" \ - -func="Register" \ - -args="" \ - -args={NAME} \ - -args="" \ - {ADDRESS} \ - > unsigned.tx -``` - -:::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 | -|--------------|---------|----------------------------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | -| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | - -#### **makeTx Call Options** - -| Name | Type | Description | -|-----------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| `send` | String | The amount of coins to send. | -| `pkgpath` | String | The package path (required). | -| `func` | String | The contract to call (required). | -| `args` | String | An argument of the function being called. Can be used multiple times in a single `call` command to accommodate possible multiple function arguments. | - -:::info -Currently, only primitive types are supported as `-args` parameters. This limitation will be addressed in the future. -Alternatively, see how `maketx run` works. -::: - -### `send` - -This subcommand lets you send a native currency to an address. - -```bash -gnokey maketx send \ - -gas-fee="1ugnot" \ - -gas-wanted="5000000" \ - -send={SEND_AMOUNT} \ - -to={TO_ADDRESS} \ - {ADDRESS} \ - > unsigned.tx -``` - -#### **SignBroadcast Options** - -| Name | Type | Description | -|--------------|---------|----------------------------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | -| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | - -#### **makeTx Send Options** - -| Name | Type | Description | -|--------|--------|--------------------------| -| `send` | String | Amount of coins to send. | -| `to` | String | The destination address. | - - -## Sign a Document - -Sign a document with the following command. - -```bash -gnokey sign -``` - -#### **Options** - -| Name | Type | Description | -|------------------|---------|------------------------------------------------------------| -| `txpath` | String | The path to file of tx to sign (default: `-`). | -| `chainid` | String | The chainid to sign for (default: `dev`). | -| `number` | UInt | The account number of the account to sign with (required) | -| `sequence` | UInt | The sequence number of the account to sign with (required) | -| `show-signbytes` | Boolean | Shows signature bytes. | - - -## Verify a Document Signature - -Verify a document signature with the following command. - -```bash -gnokey verify -``` - -#### **Options** - -| Name | Type | Description | -|-----------|--------|------------------------------------------| -| `docpath` | String | The path of the document file to verify. | - -## Broadcast a Signed Document - -Broadcast a signed document with the following command. - -```bash -gnokey broadcast {signed transaction file document} -``` - -[^1]: `only` simulates the transaction as a "dry run" (ie. without committing to - the chain), `test` performs simulation and, if successful, commits the - transaction, `skip` skips simulation entirely and commits directly. diff --git a/docs/gno-tooling/cli/gnokey/full-security-tx.md b/docs/gno-tooling/cli/gnokey/full-security-tx.md new file mode 100644 index 00000000000..bccddb30b8a --- /dev/null +++ b/docs/gno-tooling/cli/gnokey/full-security-tx.md @@ -0,0 +1,134 @@ +--- +id: full-security-tx +--- + +# Making an airgapped transaction + +## Prerequisites + +- **`gnokey` installed.** Reference the + [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps + +## Overview + +`gnokey` provides a way to create a transaction, sign it, and later +broadcast it to a chain in the most secure fashion. This approach, while more +complicated than the standard approach shown [in a previous tutorial](./state-changing-calls.md), +grants full control and provides [airgap](https://en.wikipedia.org/wiki/Air_gap_(networking)) +support. + +By separating the signing and the broadcasting steps of submitting a transaction, +users can make sure that the signing happens in a secure, offline environment, +keeping private keys away from possible exposure to attacks coming from the +internet. + +The intended purpose of this functionality is to provide maximum security when +signing and broadcasting a transaction. In practice, this procedure should take +place on two separate machines controlled by the holder of the keys, one with +access to the internet (`Machine A`), and the other one without (`Machine B`), +with the separation of steps as follows: +1. `Machine A`: Fetch account information from the chain +2. `Machine B`: Create an unsigned transaction locally +3. `Machine B`: Sign the transaction +4. `Machine A`: Broadcast the transaction + +## 1. Fetching account information from the chain + +First, we need to fetch data for the account we are using to sign the transaction, +using the [auth/accounts](./querying-a-network.md#authaccounts) query: + +```bash +gnokey query auth/accounts/ -remote "https://rpc.gno.land:443" +``` + +We need to extract the account number and sequence from the output: + +```bash +height: 0 +data: { + "BaseAccount": { + "address": "g1zzqd6phlfx0a809vhmykg5c6m44ap9756s7cjj", + "coins": "10000000ugnot", + "public_key": null, + "account_number": "468", + "sequence": "0" + } +} +``` + +In this case, the account number is `468`, and the sequence (nonce) is `0`. We +will need these values to sign the transaction later. These pieces of information +are crucial during the signing process, as they are included in the signature +of the transaction, preventing replay attacks. + +## 2. Creating an unsigned transaction locally + +To create the transaction you want, you can use the [`call` API](./state-changing-calls.md#call), +without the `-broadcast` flag, while redirecting the output to a local file: + +```bash +gnokey maketx call \ +-pkgpath "gno.land/r/demo/userbook" \ +-func "SignUp" \ +-gas-fee 1000000ugnot \ +-gas-wanted 2000000 \ +mykey > userbook.tx +``` + +This will create a `userbook.tx` file with a null `signature` field. +Now we are ready to sign the transaction. + +## 3. Signing the transaction + +To add a signature to the transaction, we can use the `gnokey sign` subcommand. +To sign, we must set the correct flags for the subcommand: +- `-tx-path` - path to the transaction file to sign, in our case, `userbook.tx` +- `-chainid` - id of the chain to sign for +- `-account-number` - number of the account fetched previously +- `-account-sequence` - sequence of the account fetched previously + +```bash +gnokey sign \ +-tx-path userbook.tx \ +-chainid "portal-loop" \ +-account-number 468 \ +-account-sequence 0 \ +mykey +``` + +After inputting the correct values, `gnokey` will ask for the password to decrypt +the keypair. Once we input the password, we should receive the message that the +signing was completed. If we open the `userbook.tx` file, we will be able to see +that the signature field has been populated. + +We are now ready to broadcast this transaction to the chain. + +## 4. Broadcasting the transaction + +To broadcast the signed transaction to the chain, we can use the `gnokey broadcast` +subcommand, giving it the path to the signed transaction: + +```bash +gnokey broadcast -remote "https://rpc.gno.land:443" userbook.tx +``` + +In this case, we do not need to specify a keypair, as the transaction has already +been signed in a previous step and `gnokey` is only sending it to the RPC endpoint. + +## Verifying a transaction's signature + +To verify a transaction's signature is correct, you can use the `gnokey verify` +subcommand. We can provide the path to the transaction document using the `-docpath` +flag, provide the key we signed the transaction with, and the signature itself. +Make sure the signature is in the `hex` format. + +```bash +gnokey verify -docpath userbook.tx mykey +``` + +## Conclusion + +That's it! 🎉 + +In this tutorial, you've learned to use `gnokey` for creating maximum-security +transactions in an airgapped manner. diff --git a/docs/gno-tooling/cli/gnokey/gnokey.md b/docs/gno-tooling/cli/gnokey/gnokey.md new file mode 100644 index 00000000000..7344f9b539c --- /dev/null +++ b/docs/gno-tooling/cli/gnokey/gnokey.md @@ -0,0 +1,16 @@ +--- +id: gnokey +--- + +# `gnokey` + +## Overview + +In this section, you will learn how to use the `gnokey` binary. `gnokey` is the +gno.land CLI keychain and client, and it allows you to do 4 main things: +- Manage Gno keypairs +- Send state-changing calls (transactions) +- Query a gno.land network +- Sign and broadcast transactions with [airgap protection](https://en.wikipedia.org/wiki/Air_gap_(networking)) + +Check out the rest of this section to learn how to do all of these. diff --git a/docs/gno-tooling/cli/gnokey/querying-a-network.md b/docs/gno-tooling/cli/gnokey/querying-a-network.md new file mode 100644 index 00000000000..1bb1bb8275f --- /dev/null +++ b/docs/gno-tooling/cli/gnokey/querying-a-network.md @@ -0,0 +1,233 @@ +--- +id: querying-a-network +--- + +# Querying a gno.land network + +## Prerequisites + +- **`gnokey` installed.** Reference the + [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps + +## Overview + +gno.land and `gnokey` support ABCI queries. Using ABCI queries, you can query the state of +a gno.land network without spending any gas. All queries need to be pointed towards +a specific remote address from which the state will be retrieved. + +To send ABCI queries, you can use the `gnokey query` subcommand, and provide it +with the appropriate query. The `query` subcommand allows us to send different +types of queries to a gno.land network. + +Below is a list of queries a user can make with `gnokey`: +- `auth/accounts/{ADDRESS}` - returns information about an account +- `bank/balances/{ADDRESS}` - returns balances of an account +- `vm/qfuncs` - returns the exported functions for a given pkgpath +- `vm/qfile` - returns package contents for a given pkgpath +- `vm/qeval` - evaluates an expression in read-only mode on and returns the results +- `vm/qrender` - shorthand for evaluating `vm/qeval Render("")` for a given pkgpath + +Let's see how we can use them. + +## `auth/accounts` + +We can obtain information about a specific address using this subquery. To call it, +we can run the following command: + +```bash +gnokey query auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -remote https://rpc.gno.land:443 +``` + +With this, we are asking the Portal Loop network to deliver information about the +specified address. If everything went correctly, we should get output similar to the following: + +```bash +height: 0 +data: { + "BaseAccount": { + "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "coins": "227984898927ugnot", + "public_key": { + "@type": "/tm.PubKeySecp256k1", + "value": "A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y" + }, + "account_number": "0", + "sequence": "12" + } +} +``` + +The return data will contain the following fields: +- `height` - the height at which the query was executed. This is currently not + supported and is `0` by default. +- `data` - contains the result of the query. + +The `data` field returns a `BaseAccount`, which is the main struct used in [TM2](../../../concepts/tendermint2.md) +to hold account data. It contains the following information: +- `address` - the address of the account +- `coins` - the list of coins the account owns +- `public_key` - the TM2 public key of the account, from which the address is derived +- `account_number` - a unique identifier for the account on the gno.land chain +- `sequence` - a nonce, used for protection against replay attacks + +## `bank/balances` + +With this query, we can fetch [coin](../../../concepts/stdlibs/coin.md) balances +of a specific account. To call it, we can run the following command: + +```bash +gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -remote https://rpc.gno.land:443 +``` + +If everything went correctly, we should get an output similar to the following: + +```bash +height: 0 +data: "227984898927ugnot" +``` + +The data field will contain the coins the address owns. + +## `vm/qfuncs` + +Using the `vm/qfuncs` query, we can fetch exported functions from a specific package +path. To specify the path we want to query, we can use the `-data` flag: + +```bash +gnokey query vm/qfuncs --data "gno.land/r/demo/wugnot" -remote https://rpc.gno.land:443 +``` + +The output is a string containing all exported functions for the `wugnot` realm: + +```json +height: 0 +data: [ + { + "FuncName": "Deposit", + "Params": null, + "Results": null + }, + { + "FuncName": "Withdraw", + "Params": [ + { + "Name": "amount", + "Type": "uint64", + "Value": "" + } + ], + "Results": null + }, + // other functions +] +``` + +## `vm/qfile` + +With the `vm/qfile` query, we can fetch files and their content found on a +specific package path. To specify the path we want to query, we can use the +`-data` flag: + +```bash +gnokey query vm/qfile -data "gno.land/r/demo/wugnot" -remote https://rpc.gno.land:443 +``` + +If the `-data` field contains only the package path, the output is a list of all +files found within the `wugnot` realm: + +```bash +height: 0 +data: gno.mod +wugnot.gno +z0_filetest.gno +``` + +If the `-data` field also specifies a file name after the path, the source code +of the file will be retrieved: + +```bash +gnokey query vm/qfile -data "gno.land/r/demo/wugnot/wugnot.gno" -remote https://rpc.gno.land:443 +``` + +Output: +```bash +height: 0 +data: package wugnot + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/demo/users" +) + +var ( + banker *grc20.Banker = grc20.NewBanker("wrapped GNOT", "wugnot", 0) + Token = banker.Token() +) + +const ( + ugnotMinDeposit uint64 = 1000 + wugnotMinDeposit uint64 = 1 +) +... +``` + +## `vm/qeval` + +`vm/qeval` allows us to evaluate a call to an exported function without using gas, +in read-only mode. For example: + +```bash +gnokey query vm/qeval -remote https://rpc.gno.land:443 -data "gno.land/r/demo/wugnot.BalanceOf(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")" +``` + +This command will return the `wugnot` balance of the above address without using gas. +Properly escaping quotation marks for string arguments is currently required. + +Currently, `vm/qeval` only supports primitive types in expressions. + +## `vm/qrender` + +`vm/qrender` is an alias for executing `vm/qeval` on the `Render("")` function. +We can use it like this: + +```bash +gnokey query vm/qrender --data "gno.land/r/demo/wugnot:" -remote https://rpc.gno.land:443 +``` + +Running this command will display the current `Render()` output of the WUGNOT +realm, which is also displayed by default on the [realm's page](https://gno.land/r/demo/wugnot): + +```bash +height: 0 +data: # wrapped GNOT ($wugnot) + +* **Decimals**: 0 +* **Total supply**: 5012404 +* **Known accounts**: 2 +``` + +:::info Specifying a path to `Render()` + +To call the `vm/qrender` query with a specific path, use the `:` syntax. +For example, the `wugnot` realm provides a way to display the balance of a specific +address in its `Render()` function. We can fetch the balance of an account by +providing the following custom pattern to the `wugnot` realm: + +```bash +gnokey query vm/qrender --data "gno.land/r/demo/wugnot:balance/g125em6arxsnj49vx35f0n0z34putv5ty3376fg5" -remote https://rpc.gno.land:443 +``` + +To see how this was achieved, check out `wugnot`'s `Render()` function. +::: + +## Conclusion + +That's it! 🎉 + +In this tutorial, you've learned to use `gnokey` to query a gno.land +network. diff --git a/docs/gno-tooling/cli/gnokey/state-changing-calls.md b/docs/gno-tooling/cli/gnokey/state-changing-calls.md new file mode 100644 index 00000000000..79a777cca51 --- /dev/null +++ b/docs/gno-tooling/cli/gnokey/state-changing-calls.md @@ -0,0 +1,466 @@ +--- +id: state-changing-calls +--- + +# Making state-changing calls (transactions) + +## Prerequisites + +- **`gnokey` installed.** Reference the + [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps + +## Overview + +In Gno, there are four types of messages that can change on-chain state: +- `AddPackage` - adds new code to the chain +- `Call` - calls a specific path and function on the chain +- `Send` - sends coins from one address to another +- `Run` - executes a Gno script against on-chain code + +A gno.land transaction contains two main things: +- A base configuration where variables such as `gas-fee`, `gas-wanted`, and others + are defined +- A list of messages to execute on the chain + +Currently, `gnokey` supports single-message transactions, while multiple-message +transactions can be created in Go programs, supported by the +[gnoclient](../../../reference/gnoclient/gnoclient.md) package. + +We will need some testnet coins (GNOTs) for each state-changing call. Visit the [Faucet +Hub](https://faucet.gno.land) to get GNOTs for the Gno testnets that are currently live. + +Let's delve deeper into each of these message types. + +## `AddPackage` + +In case you want to upload new code to the chain, you can use the `AddPackage` +message type. You can send an `AddPackage` transaction with `gnokey` using the +following command: + +```bash +gnokey maketx addpkg +``` + +To understand how to use this subcommand better, let's write a simple "Hello world" +[pure package](../../../concepts/packages.md). First, let's create a folder which will +store our example code. + +```bash +└── example/ +``` + +Then, let's create a `hello_world.gno` file under the `p/` folder: + +```bash +cd example +mkdir p/ && cd p +touch hello_world.gno +``` + +Now, we should have the following folder structure: + +```bash +└── example/ +│ └── p/ +│ └── hello_world.gno +``` + +In the `hello_world.gno` file, add the following code: + +```go +package hello_world + +func Hello() string { + return "Hello, world!" +} +``` + +We are now ready to upload this package to the chain. To do this, we must set the +correct flags for the `addpkg` subcommand. + +The `addpkg` subcommmand uses the following flags and arguments: +- `-pkgpath` - on-chain path where your code will be uploaded to +- `-pkgdir` - local path where your is located +- `-broadcast` - enables broadcasting the transaction to the chain +- `-send` - a deposit amount of GNOT to send along with the transaction +- `-gas-wanted` - the upper limit for units of gas for the execution of the + transaction +- `-gas-fee` - amount of GNOTs to pay per gas unit +- `-chain-id` - id of the chain that we are sending the transaction to +- `-remote` - specifies the remote node RPC listener address + +The `-pkgpath` and `-pkgdir` flags are unique to the `addpkg` subcommand, while +`-broadcast`,`-send`, `-gas-wanted`, `-gas-fee`, `-chain-id`, and `-remote` are +used for setting the base transaction configuration. These flags will be repeated +throughout the tutorial. + +Next, let's configure the `addpkg` subcommand to publish this package to the +[Portal Loop](../../../concepts/portal-loop.md) testnet. Assuming we are in +the `example/p/` folder, the command will look like this: + +```bash +gnokey maketx addpkg \ +-pkgpath "gno.land/p//hello_world" \ +-pkgdir "." \ +-send "" \ +-gas-fee 10000000ugnot \ +-gas-wanted 8000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" +``` + +Once we have added a desired [namespace](../../../concepts/namespaces.md) to upload the package to, we can specify +a keypair name to use to execute the transaction: + +```bash +gnokey maketx addpkg \ +-pkgpath "gno.land/p/examplenamespace/hello_world" \ +-pkgdir "." \ +-send "" \ +-gas-fee 10000000ugnot \ +-gas-wanted 200000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" +mykey +``` + +If the transaction was successful, you will get output from `gnokey` that is similar to the following: + +``` +OK! +GAS WANTED: 200000 +GAS USED: 117564 +HEIGHT: 3990 +EVENTS: [] +TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM= +``` + +Let's analyze the output, which is standard for any `gnokey` transaction: +- `GAS WANTED: 200000` - the original amount of gas specified for the transaction +- `GAS USED: 117564` - the gas used to execute the transaction +- `HEIGHT: 3990` - the block number at which the transaction was executed at +- `EVENTS: []` - [Gno events](../../../concepts/stdlibs/events.md) emitted by the transaction, in this case, none +- `TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM=` - the hash of the transaction + +Congratulations! You have just uploaded a pure package to the Portal Loop network. +If you wish to deploy to a different network, find the list of all network +configurations in the [Network Configuration](../../../reference/network-config.md) section. + +## `Call` + +The `Call` message type is used to call any exported realm function. +You can send a `Call` transaction with `gnokey` using the following command: + +```bash +gnokey maketx call +``` + +:::info `Call` uses gas + +Using `Call` to call an exported function will use up gas, even if the function +does not modify on-chain state. If you are calling such a function, you can use +the [`query` functionality](./querying-a-network.md) for a read-only call which +does not use gas. + +::: + +For this example, we will call the `wugnot` realm, which wraps GNOTs to a +GRC20-compatible token called `wugnot`. We can find this realm deployed on the +[Portal Loop](../../../concepts/portal-loop.md) testnet, under the `gno.land/r/demo/wugnot` path. + +We will wrap `1000ugnot` into the equivalent in `wugnot`. To do this, we can call +the `Deposit()` function found in the `wugnot` realm. As previously, we will +configure the `maketx call` subcommand: + +```bash +gnokey maketx call \ +-pkgpath "gno.land/r/demo/wugnot" \ +-func "Deposit" \ +-send "1000ugnot" \ +-gas-fee 10000000ugnot \ +-gas-wanted 2000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey +``` + +In this command, we have specified three main things: +- The path where the realm lives on-chain with the `-pkgpath` flag +- The function that we want to call on the realm with the `-func` flag +- The amount of `ugnot` we want to send to be wrapped, using the `-send` flag + +Apart from this, we have also specified the Portal Loop chain ID, `portal-loop`, +as well as the Portal Loop remote address, `https://rpc.gno.land:443`. + +After running the command, we can expect an output similar to the following: +```bash +OK! +GAS WANTED: 2000000 +GAS USED: 489528 +HEIGHT: 24142 +EVENTS: [{"type":"Transfer","attrs":[{"key":"from","value":""},{"key":"to","value":"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"},{"key":"value","value":"1000"}],"pkg_path":"gno.land/r/demo/wugnot","func":"Mint"}] +TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM= +``` + +In this case, we can see that the `Deposit()` function emitted an +[event](../../../concepts/stdlibs/events.md) that tells us more about what +happened during the transaction. + +After broadcasting the transaction, we can verify that we have the amount of `wugnot` we expect. We +can call the `BalanceOf()` function in the same realm: + +```bash +gnokey maketx call \ +-pkgpath "gno.land/r/demo/wugnot" \ +-func "BalanceOf" \ +-args "" \ +-gas-fee 10000000ugnot \ +-gas-wanted 2000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey +``` + +If everything was successful, we should get something similar to the following +output: + +``` +(1000 uint64) + +OK! +GAS WANTED: 2000000 +GAS USED: 396457 +HEIGHT: 64839 +EVENTS: [] +TX HASH: gQP9fJYrZMTK3GgRiio3/V35smzg/jJ62q7t4TLpdV4= +``` + +At the top, you will see the output of the transaction, specifying the value and +type of the return argument. + +In this case, we used `maketx call` to call a read-only function, which simply +checks the `wugnot` balance of a specific address. This is discouraged, as +`maketx call` actually uses gas. To call a read-only function without spending gas, +check out the `vm/qeval` query in the [Querying a network](./querying-a-network.md#vmqeval) section. + +## `Send` + +We can use the `Send` message type to access the TM2 [Banker](../../../concepts/stdlibs/banker.md) +directly and transfer coins from one Gno address to another. + +Coins, such as GNOTs, are always formatted in the following way: + +``` + +100ugnot +``` + +For this example, let's transfer some GNOTs. Just like before, we can configure +our `maketx send` subcommand: +```bash +gnokey maketx send \ +-to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 \ +-send 100ugnot \ +-gas-fee 10000000ugnot \ +-gas-wanted 2000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey +``` + +Here, we have set the `-to` & `-send` flags to match the recipient, in this case +the publicly-known `test1` address, and `100ugnot` for the coins we want to send, +respectively. + +To check the balance of a specific address, check out the `bank/balances` query +in the [Querying a network](./querying-a-network.md#bankbalances) section. + +## `Run` + +With the `Run` message, you can write a snippet of Gno code and run it against +code on the chain. For this example, we will use the [Userbook realm](https://gno.land/r/demo/userbook), +which simply allows you to register the fact that you have interacted with it. +It contains a simple `SignUp()` function, which we will call with `Run`. + +To understand how to use the `Run` message better, let's write a simple `script.gno` +file. First, create a folder which will store our script. + +```bash +└── example/ +``` + +Then, let's create a `script.gno` file: + +```bash +cd example +touch script.gno +``` + +Now, we should have the following folder structure: + +```bash +└── example/ +│ └── script.gno +``` + +In the `script.gno` file, first define the package to be `main`. Then we can import +the Userbook realm and define a `main()` function with no return values which will +be automatically detected and run. In it, we can call the `SignUp()` function. + +```go +package main + +import "gno.land/r/demo/userbook" + +func main() { + println(userbook.SignUp()) +} +``` + +Now we will be able to provide this to the `maketx run` subcommand: +```bash +gnokey maketx run \ +-gas-fee 1000000ugnot \ +-gas-wanted 20000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey ./script.gno +``` + +After running this command, the chain will execute the script and apply any state +changes. Additionally, by using `println`, which is only available in the `Run` +& testing context, we will be able to see the return value of the function called. + +### The power of `Run` + +Specifically, the above example could have been replaced with a simple `maketx call` +call. The full potential of run comes out in three specific cases: +1. Calling realm functions multiple times in a loop +2. Calling functions with non-primitive input arguments +3. Calling methods on exported variables + +Let's look at each of these cases in detail. To demonstrate, we'll make a call +to the following example realm: + +```go +package foo + +import "gno.land/p/demo/ufmt" + +var ( + MainFoo *Foo + foos []*Foo +) + +type Foo struct { + bar string + baz int +} + +func init() { + MainFoo = &Foo{bar: "mainBar", baz: 0} +} + +func (f *Foo) String() string { + return ufmt.Sprintf("Foo - (bar: %s) - (baz: %d)\n\n", f.bar, f.baz) +} + +func NewFoo(bar string, baz int) *Foo { + return &Foo{bar: bar, baz: baz} +} + +func AddFoos(multipleFoos []*Foo) { + foos = append(foos, multipleFoos...) +} + +func Render(_ string) string { + var output string + + for _, f := range foos { + output += f.String() + } + + return output +} +``` + +This realm is deployed to [`gno.land/r/docs/examples/run/foo`](https://gno.land/r/docs/examples/run/foo/package.gno) +on the Portal Loop testnet. + +1. Calling realm functions multiple times in a loop: +```go +package main + +import ( + "gno.land/r/docs/examples/run/foo" +) + +func main() { + for i := 0; i < 5; i++ { + println(foo.Render("")) + } +} +``` + +2. Calling functions with non-primitive input arguments: + +Currently, `Call` only supports primitives for arguments. With `Run`, these +limitations are removed; we can execute a function that takes in a struct, array, +or even an array of structs. + +We are unable to call `AddFoos()` with the `Call` message type, while with `Run`, +we can: + +```go +package main + +import ( + "strconv" + + "gno.land/r/docs/examples/run/foo" +) + +func main() { + var multipleFoos []*foo.Foo + + for i := 0; i < 5; i++ { + newFoo := foo.NewFoo( + "bar"+strconv.Itoa(i), + i, + ) + + multipleFoos = append(multipleFoos, newFoo) + } + + foo.AddFoos(multipleFoos) +} + +``` + +3. Calling methods on exported variables: + +```go +package main + +import "gno.land/r/docs/examples/run/foo" + +func main() { + println(foo.MainFoo.String()) +} +``` + +Finally, we can call methods that are on top-level objects in case they exist, +which is not currently possible with the `Call` message. + +## Conclusion + +That's it! 🎉 + +In this tutorial, you've learned to use `gnokey` for sending multiple types of +state-changing calls to a gno.land chain. diff --git a/docs/getting-started/local-setup/working-with-key-pairs.md b/docs/gno-tooling/cli/gnokey/working-with-key-pairs.md similarity index 75% rename from docs/getting-started/local-setup/working-with-key-pairs.md rename to docs/gno-tooling/cli/gnokey/working-with-key-pairs.md index 23516c44e6c..ba03ca569b4 100644 --- a/docs/getting-started/local-setup/working-with-key-pairs.md +++ b/docs/gno-tooling/cli/gnokey/working-with-key-pairs.md @@ -5,14 +5,16 @@ id: working-with-key-pairs # Working with Key Pairs ## Overview + In this tutorial, you will learn how to manage private user keys, which are required for interacting with the gno.land blockchain. You will understand what mnemonics are, how they are used, and how you can make interaction seamless with Gno. ## Prerequisites -- **`gnokey` installed.** Reference the -[Local Setup](installation.md#2-installing-the-required-tools-) guide for steps + +- **`gnokey` installed.** Reference the + [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps ## Listing available keys `gnokey` works by creating a local directory in the filesystem for storing @@ -31,27 +33,27 @@ Example output: USAGE [flags] [...] -Manages private keys for the node +gno.land keychain & client SUBCOMMANDS - add Adds key to the keybase - delete Deletes a key from the keybase - generate Generates a bip39 mnemonic - export Exports private key armor - import Imports encrypted private key armor - list Lists all keys in the keybase - sign Signs the document - verify Verifies the document signature - query Makes an ABCI query - broadcast Broadcasts a signed document - maketx Composes a tx document to sign + add adds key to the keybase + delete deletes a key from the keybase + generate generates a bip39 mnemonic + export exports private key armor + import imports encrypted private key armor + list lists all keys in the keybase + sign signs the given tx document and saves it to disk + verify verifies the document signature + query makes an ABCI query + broadcast broadcasts a signed document + maketx composes a tx document to sign FLAGS - -config ... config file (optional) - -home $XDG_CONFIG/gno home directory - -insecure-password-stdin=false WARNING! take password from stdin - -quiet=false suppress output during execution - -remote 127.0.0.1:26657 remote node URL + -config ... config file (optional) + -home $XDG_CONFIG/gno home directory + -insecure-password-stdin=false WARNING! take password from stdin + -quiet=false suppress output during execution + -remote 127.0.0.1:26657 remote node URL ``` In this example, the directory where `gnokey` will store working data @@ -97,7 +99,7 @@ To generate the mnemonic phrase in the console, you can run: gnokey generate ``` -![gnokey generate](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-generate.gif) +![gnokey generate](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-generate.gif) ## Adding a random private key If we wanted to add a new private key to the keystore, we can run the following @@ -115,7 +117,7 @@ After you enter the password, the `gnokey` tool will add the key to the keystore and return the accompanying [mnemonic phrase](https://en.bitcoin.it/wiki/Seed_phrase), which you should remember somewhere if you want to recover the key at a future point in time. -![gnokey add random](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif) +![gnokey add random](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif) You can check that the key was indeed added to the keystore, by listing available keys: @@ -124,7 +126,7 @@ keys: gnokey list ``` -![gnokey list](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-list.gif) +![gnokey list](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-list.gif) ## Adding a private key using a mnemonic To add a private key to the `gnokey` keystore [using an existing mnemonic](#generating-a-bip39-mnemonic), @@ -140,7 +142,7 @@ Of course, you can replace `MyKey` with whatever name you want for your key. By following the prompts to encrypt the key on disk, and providing a BIP39 mnemonic, we can successfully add the key to the keystore. -![gnokey add mnemonic](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-mnemonic.gif) +![gnokey add mnemonic](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-mnemonic.gif) ## Deleting a private key To delete a private key from the `gnokey` keystore, we need to know the name or @@ -175,7 +177,7 @@ Follow the prompts presented in the terminal. Namely, you will be asked to decrypt the key in the keystore, and later to encrypt the armor file on disk. It is worth noting that you can also export unencrypted key armor, using the `--unsafe` flag. -![gnokey export](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-export.gif) +![gnokey export](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-export.gif) ## Importing a private key If you have an exported private key file, you can import it into `gnokey` fairly @@ -195,4 +197,11 @@ encryption password for storing the key in the keystore. After executing the previous command, the `gnokey` keystore will have imported `ImportedKey`. -![gnokey import](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-import.gif) +![gnokey import](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-import.gif) + +## Conclusion + +That's it! 🎉 + +In this tutorial, you've learned to use `gnokey` for managing Gno keypairs. + diff --git a/docs/how-to-guides/connecting-from-go.md b/docs/how-to-guides/connecting-from-go.md index 4926f700a4d..971007e5cef 100644 --- a/docs/how-to-guides/connecting-from-go.md +++ b/docs/how-to-guides/connecting-from-go.md @@ -15,7 +15,7 @@ For this guide, we will build a small Go app that will: ## Prerequisites - A local gno.land keypair generated using -[gnokey](../getting-started/local-setup/working-with-key-pairs.md) +[gnokey](../gno-tooling/cli/gnokey/working-with-key-pairs.md) ## Setup @@ -223,11 +223,21 @@ message type. We will use the wrapped ugnot realm for this example, wrapping `1000000ugnot` (1 $GNOT) for demonstration purposes. ```go -msg := gnoclient.MsgCall{ - PkgPath: "gno.land/r/demo/wugnot", // wrapped ugnot realm path - FuncName: "Deposit", // function to call - Args: nil, // arguments in string format - Send: "1000000ugnot", // coins to send along with transaction +import ( + ... + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/std" +) +``` + +```go +msg := vm.MsgCall{ + Caller: addr, // address of the caller (signer) + PkgPath: "gno.land/r/demo/wugnot", // wrapped ugnot realm path + Func: "Deposit", // function to call + Args: nil, // arguments in string format + Send: std.Coins{{Denom: ugnot.Denom, Amount: int64(1000000)}}, // coins to send along with transaction } ``` diff --git a/docs/how-to-guides/deploy.md b/docs/how-to-guides/deploy.md index 620a5664f7c..1e27ccd0cad 100644 --- a/docs/how-to-guides/deploy.md +++ b/docs/how-to-guides/deploy.md @@ -129,7 +129,7 @@ given in the `--gas-wanted` flag to cover the deployment cost. Regardless of whether you're deploying a realm or a package, you will be using `gnokey`'s `maketx addpkg` - the usage of `maketx addpkg` in both cases is identical. To read more about the `maketx addpkg` -subcommand, view the `gnokey` [reference](../gno-tooling/cli/gnokey.md#addpkg). +subcommand, view the `gnokey` [reference](../gno-tooling/cli/gnokey/state-changing-calls.md#addpackage). ::: diff --git a/docs/reference/gno-js-client/gno-provider.md b/docs/reference/gno-js-client/gno-provider.md index df808106cc3..1b9cbd53652 100644 --- a/docs/reference/gno-js-client/gno-provider.md +++ b/docs/reference/gno-js-client/gno-provider.md @@ -116,7 +116,7 @@ Returns **Promise** #### Usage ```ts -await provider.getFileContent('gno.land/r/demo/foo20', 'TotalSupply()') +await provider.getFileContent('gno.land/r/demo/foo20') /* foo20.gno foo20_test.gno diff --git a/docs/reference/gnoclient/gnoclient.md b/docs/reference/gnoclient/gnoclient.md index 0c6c0d87308..672a3772bb7 100644 --- a/docs/reference/gnoclient/gnoclient.md +++ b/docs/reference/gnoclient/gnoclient.md @@ -14,7 +14,7 @@ APIs for common functionality. - Use local keystore to sign & broadcast transactions containing any type of Gno message - Sign & broadcast transactions with batch messages -- Use [ABCI queries](../../gno-tooling/cli/gnokey.md#make-an-abci-query) in +- Use [ABCI queries](../../gno-tooling/cli/gnokey/querying-a-network.md) in your Go code ## Installation @@ -30,5 +30,5 @@ To see the full reference documentation for the `gnoclient` package, we recommen 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) +["How to connect a Go app to gno.land"](../../how-to-guides/connecting-from-go.md). diff --git a/docs/reference/network-config.md b/docs/reference/network-config.md index 97c2fc886e9..6d4fc9ea14a 100644 --- a/docs/reference/network-config.md +++ b/docs/reference/network-config.md @@ -7,9 +7,9 @@ id: network-config | 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 | http://rpc.staging.gno.land:36657 | `staging` | +| Test4 | https://rpc.test4.gno.land:443 | `test4` | +| Test3 | https://rpc.test3.gno.land:443 | `test3` | +| Staging | https://rpc.staging.gno.land:443 | `staging` | ### WebSocket endpoints All networks follow the same pattern for websocket connections: diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index f8d3cba13bb..089de682cfd 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -85,7 +85,7 @@ Returns the original signer of the transaction. #### Usage ```go -caller := std.GetOrigSend() +caller := std.GetOrigCaller() ``` --- 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/avlhelpers/avlhelpers.gno b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno new file mode 100644 index 00000000000..27842932dd3 --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -0,0 +1,41 @@ +package avlhelpers + +import ( + "gno.land/p/demo/avl" +) + +// Iterate the keys in-order starting from the given prefix. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +// The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. +func IterateByteStringKeysByPrefix(tree avl.Tree, prefix string, cb avl.IterCbFn) { + end := "" + n := len(prefix) + // To make the end of the search, increment the final character ASCII by one. + for n > 0 { + if ascii := int(prefix[n-1]); ascii < 0xff { + end = prefix[0:n-1] + string(ascii+1) + break + } + + // The last character is 0xff. Try the previous character. + n-- + } + + tree.Iterate(prefix, end, cb) +} + +// Get a list of keys starting from the given prefix. Limit the +// number of results to maxResults. +// The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. +func ListByteStringKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string { + result := []string{} + IterateByteStringKeysByPrefix(tree, prefix, func(key string, value interface{}) bool { + result = append(result, key) + if len(result) >= maxResults { + return true + } + return false + }) + return result +} diff --git a/examples/gno.land/r/sys/names/gno.mod b/examples/gno.land/p/demo/avlhelpers/gno.mod similarity index 55% rename from examples/gno.land/r/sys/names/gno.mod rename to examples/gno.land/p/demo/avlhelpers/gno.mod index 97236a84892..559f60975cf 100644 --- a/examples/gno.land/r/sys/names/gno.mod +++ b/examples/gno.land/p/demo/avlhelpers/gno.mod @@ -1,3 +1,3 @@ -module gno.land/r/sys/names +module gno.land/p/demo/avlhelpers require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno new file mode 100644 index 00000000000..1c7873e297a --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno @@ -0,0 +1,91 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "encoding/hex" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avlhelpers" + "gno.land/p/demo/ufmt" +) + +func main() { + tree := avl.Tree{} + + { + // Empty tree. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + } + + tree.Set("alice", "") + tree.Set("andy", "") + tree.Set("bob", "") + + { + // Match only alice. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "al", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + } + + { + // Match alice and andy. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + println("match: " + matches[1]) + } + + { + // Match alice and andy limited to 1. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a", 1) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + } + + tree = avl.Tree{} + tree.Set("a\xff", "") + tree.Set("a\xff\xff", "") + tree.Set("b", "") + tree.Set("\xff\xff\x00", "") + + { + // Match only "a\xff\xff". + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a\xff\xff", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) + } + + { + // Match "a\xff" and "a\xff\xff". + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a\xff", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[1])))) + } + + { + // Edge case: Match only "\xff\xff\x00". + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "\xff\xff", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) + } +} + +// Output: +// # matches: 0 +// # matches: 1 +// match: alice +// # matches: 2 +// match: alice +// match: andy +// # matches: 1 +// match: alice +// # matches: 1 +// match: 61ffff +// # matches: 2 +// match: 61ff +// match: 61ffff +// # matches: 1 +// match: ffff00 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/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno new file mode 100644 index 00000000000..5e35b8c7227 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -0,0 +1,89 @@ +// Entropy generates fully deterministic, cost-effective, and hard to guess +// numbers. +// +// It is designed both for single-usage, like seeding math/rand or for being +// reused which increases the entropy and its cost effectiveness. +// +// Disclaimer: this package is unsafe and won't prevent others to guess values +// in advance. +// +// It uses the Bernstein's hash djb2 to be CPU-cycle efficient. +package entropy + +import ( + "math" + "std" + "time" +) + +type Instance struct { + value uint32 +} + +func New() *Instance { + r := Instance{value: 5381} + r.addEntropy() + return &r +} + +func FromSeed(seed uint32) *Instance { + r := Instance{value: seed} + r.addEntropy() + return &r +} + +func (i *Instance) Seed() uint32 { + return i.value +} + +func (i *Instance) djb2String(input string) { + for _, c := range input { + i.djb2Uint32(uint32(c)) + } +} + +// super fast random algorithm. +// http://www.cse.yorku.ca/~oz/hash.html +func (i *Instance) djb2Uint32(input uint32) { + i.value = (i.value << 5) + i.value + input +} + +// AddEntropy uses various runtime variables to add entropy to the existing seed. +func (i *Instance) addEntropy() { + // FIXME: reapply the 5381 initial value? + + // inherit previous entropy + // nothing to do + + // handle callers + { + caller1 := std.GetCallerAt(1).String() + i.djb2String(caller1) + caller2 := std.GetCallerAt(2).String() + i.djb2String(caller2) + } + + // height + { + height := std.GetHeight() + if height >= math.MaxUint32 { + height -= math.MaxUint32 + } + i.djb2Uint32(uint32(height)) + } + + // time + { + secs := time.Now().Second() + i.djb2Uint32(uint32(secs)) + nsecs := time.Now().Nanosecond() + i.djb2Uint32(uint32(nsecs)) + } + + // FIXME: compute other hard-to-guess but deterministic variables, like real gas? +} + +func (i *Instance) Value() uint32 { + i.addEntropy() + return i.value +} diff --git a/examples/gno.land/p/demo/entropy/entropy_test.gno b/examples/gno.land/p/demo/entropy/entropy_test.gno new file mode 100644 index 00000000000..0deb3ab9aa2 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/entropy_test.gno @@ -0,0 +1,46 @@ +package entropy + +import ( + "std" + "strconv" + "testing" +) + +func TestInstance(t *testing.T) { + instance := New() + if instance == nil { + t.Errorf("instance should not be nil") + } +} + +func TestInstanceValue(t *testing.T) { + baseEntropy := New() + baseResult := computeValue(t, baseEntropy) + + sameHeightEntropy := New() + sameHeightResult := computeValue(t, sameHeightEntropy) + + if baseResult != sameHeightResult { + t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) + } + + std.TestSkipHeights(1) + differentHeightEntropy := New() + differentHeightResult := computeValue(t, differentHeightEntropy) + + if baseResult == differentHeightResult { + t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult) + } +} + +func computeValue(t *testing.T, r *Instance) string { + t.Helper() + + out := "" + for i := 0; i < 10; i++ { + val := int(r.Value()) + out += strconv.Itoa(val) + " " + } + + return out +} diff --git a/examples/gno.land/p/demo/entropy/gno.mod b/examples/gno.land/p/demo/entropy/gno.mod new file mode 100644 index 00000000000..9a6db8f5b61 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/entropy diff --git a/examples/gno.land/p/demo/entropy/z_filetest.gno b/examples/gno.land/p/demo/entropy/z_filetest.gno new file mode 100644 index 00000000000..85ed1b10a3d --- /dev/null +++ b/examples/gno.land/p/demo/entropy/z_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/entropy" +) + +func main() { + // initial + println("---") + r := entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + // should be the same + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + std.TestSkipHeights(1) + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) +} + +// Output: +// --- +// 4129293727 +// 2141104956 +// 1950222777 +// 3348280598 +// 438354259 +// --- +// 4129293727 +// 2141104956 +// 1950222777 +// 3348280598 +// 438354259 +// --- +// 49506731 +// 1539580078 +// 2695928529 +// 1895482388 +// 3462727799 diff --git a/examples/gno.land/p/demo/fqname/fqname.gno b/examples/gno.land/p/demo/fqname/fqname.gno index a877a041ef9..d28453e5c1b 100644 --- a/examples/gno.land/p/demo/fqname/fqname.gno +++ b/examples/gno.land/p/demo/fqname/fqname.gno @@ -47,6 +47,7 @@ func Parse(fqname string) (pkgpath, name string) { // fmt.Println("Fully Qualified Name:", fqName) // // Output: gno.land/r/demo/foo20.GRC20 func Construct(pkgpath, name string) string { + // TODO: ensure pkgpath is valid - and as such last part does not contain a dot. if name == "" { return pkgpath } 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/grc1155/basic_grc1155_token_test.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno index 701f5c95afe..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 @@ -3,34 +3,26 @@ package grc1155 import ( "std" "testing" + + "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") @@ -39,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") @@ -90,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") @@ -181,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") @@ -229,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") @@ -284,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") @@ -331,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") @@ -377,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") @@ -421,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 8f3f36019f9..d6db0700146 100644 --- a/examples/gno.land/p/demo/grc/grc1155/gno.mod +++ b/examples/gno.land/p/demo/grc/grc1155/gno.mod @@ -2,5 +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 ) diff --git a/examples/gno.land/p/demo/grc/grc20/token.gno b/examples/gno.land/p/demo/grc/grc20/token.gno index 7f3b784932a..c9e125261b5 100644 --- a/examples/gno.land/p/demo/grc/grc20/token.gno +++ b/examples/gno.land/p/demo/grc/grc20/token.gno @@ -7,7 +7,7 @@ import ( // token implements the Token interface. // // It is generated with Banker.Token(). -// It can safely be explosed publicly. +// It can safely be exposed publicly. type token struct { banker *Banker } 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 9f4a1e12792..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 @@ -3,6 +3,8 @@ package grc721 import ( "std" "testing" + + "gno.land/p/demo/uassert" ) var ( @@ -12,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") @@ -197,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") @@ -228,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") @@ -273,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") @@ -355,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") @@ -380,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 061ea2988c3..9e1d6f56ffc 100644 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -3,5 +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 ) 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 ec152f5d20d..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,6 +5,7 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" ) func TestSetMetadata(t *testing.T) { @@ -49,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{ @@ -67,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 @@ -88,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 3087fe35d13..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,13 +5,12 @@ import ( "testing" "gno.land/p/demo/testutils" + "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") @@ -30,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 @@ -50,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/int256/arithmetic.gno b/examples/gno.land/p/demo/int256/arithmetic.gno index ce05426f585..8926fe1d6de 100644 --- a/examples/gno.land/p/demo/int256/arithmetic.gno +++ b/examples/gno.land/p/demo/int256/arithmetic.gno @@ -5,23 +5,23 @@ import "gno.land/p/demo/uint256" func (z *Int) Add(x, y *Int) *Int { z.initiateAbs() - neg := x.neg - if x.neg == y.neg { - // x + y == x + y - // (-x) + (-y) == -(x + y) - z.abs = z.abs.Add(x.abs, y.abs) + // If both numbers have the same sign, add their absolute values + z.abs.Add(x.abs, y.abs) + z.neg = x.neg } else { - // x + (-y) == x - y == -(y - x) - // (-x) + y == y - x == -(x - y) - if x.abs.Cmp(y.abs) >= 0 { - z.abs = z.abs.Sub(x.abs, y.abs) - } else { - neg = !neg - z.abs = z.abs.Sub(y.abs, x.abs) + switch x.abs.Cmp(y.abs) { + case 1: // x > y + z.abs.Sub(x.abs, y.abs) + z.neg = x.neg + case -1: // x < y + z.abs.Sub(y.abs, x.abs) + z.neg = y.neg + case 0: // x == y + z.abs = uint256.NewUint(0) } } - z.neg = neg // 0 has no sign + return z } @@ -66,22 +66,27 @@ func AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool { func (z *Int) Sub(x, y *Int) *Int { z.initiateAbs() - neg := x.neg if x.neg != y.neg { - // x - (-y) == x + y - // (-x) - y == -(x + y) - z.abs = z.abs.Add(x.abs, y.abs) + // If sign are different, add the absolute values + z.abs.Add(x.abs, y.abs) + z.neg = x.neg } else { - // x - y == x - y == -(y - x) - // (-x) - (-y) == y - x == -(x - y) - if x.abs.Cmp(y.abs) >= 0 { - z.abs = z.abs.Sub(x.abs, y.abs) - } else { - neg = !neg - z.abs = z.abs.Sub(y.abs, x.abs) + switch x.abs.Cmp(y.abs) { + case 1: // x > y + z.abs.Sub(x.abs, y.abs) + z.neg = x.neg + case -1: // x < y + z.abs.Sub(y.abs, x.abs) + z.neg = !x.neg + case 0: // x == y + z.abs = uint256.NewUint(0) } } - z.neg = neg // 0 has no sign + + // Ensure zero is always positive + if z.abs.IsZero() { + z.neg = false + } return z } @@ -107,7 +112,7 @@ func (z *Int) Mul(x, y *Int) *Int { z.initiateAbs() z.abs = z.abs.Mul(x.abs, y.abs) - z.neg = x.neg != y.neg // 0 has no sign + z.neg = x.neg != y.neg && !z.abs.IsZero() // 0 has no sign return z } @@ -126,12 +131,13 @@ func (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int { func (z *Int) Div(x, y *Int) *Int { z.initiateAbs() - z.abs.Div(x.abs, y.abs) - if x.neg == y.neg { - z.neg = false - } else { - z.neg = true + if y.abs.IsZero() { + panic("division by zero") } + + z.abs.Div(x.abs, y.abs) + z.neg = (x.neg != y.neg) && !z.abs.IsZero() // 0 has no sign + return z } diff --git a/examples/gno.land/p/demo/int256/arithmetic_test.gno b/examples/gno.land/p/demo/int256/arithmetic_test.gno index c4aeb18e3c5..4cfa306890a 100644 --- a/examples/gno.land/p/demo/int256/arithmetic_test.gno +++ b/examples/gno.land/p/demo/int256/arithmetic_test.gno @@ -15,8 +15,9 @@ func TestAdd(t *testing.T) { {"1", "1", "2"}, {"1", "2", "3"}, // NEGATIVE - {"-1", "1", "-0"}, // TODO: remove negative sign for 0 ?? + {"-1", "1", "0"}, {"1", "-1", "0"}, + {"3", "-3", "0"}, {"-1", "-1", "-2"}, {"-1", "-2", "-3"}, {"-1", "3", "2"}, @@ -188,10 +189,10 @@ func TestSub(t *testing.T) { {"1", "1", "0"}, {"-1", "1", "-2"}, {"1", "-1", "2"}, - {"-1", "-1", "-0"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-0"}, + {"-1", "-1", "0"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0"}, {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {x: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", y: "1", want: "-0"}, + {x: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", y: "1", want: "0"}, } for _, tc := range tests { @@ -351,46 +352,45 @@ func TestMulUint256(t *testing.T) { func TestDiv(t *testing.T) { tests := []struct { - x, y, want string + x, y, expected string }{ + {"1", "1", "1"}, {"0", "1", "0"}, - {"0", "-1", "-0"}, - {"10", "0", "0"}, - {"10", "1", "10"}, - {"10", "-1", "-10"}, - {"-10", "0", "-0"}, - {"-10", "1", "-10"}, - {"-10", "-1", "10"}, - {"10", "-3", "-3"}, - {"10", "3", "3"}, + {"-1", "1", "-1"}, + {"1", "-1", "-1"}, + {"-1", "-1", "1"}, + {"-6", "3", "-2"}, + {"10", "-2", "-5"}, + {"-10", "3", "-3"}, + {"7", "3", "2"}, + {"-7", "3", "-2"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, // Max uint256 / 2 } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.Div(x, y) - - if got.Neq(want) { - t.Errorf("Div(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } + for _, tt := range tests { + t.Run(tt.x+"/"+tt.y, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + result := Zero().Div(x, y) + if result.ToString() != tt.expected { + t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) + } + if result.abs.IsZero() && result.neg { + t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) + } + }) } + + t.Run("Division by zero", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Div(1, 0) did not panic") + } + }() + x := MustFromDecimal("1") + y := MustFromDecimal("0") + Zero().Div(x, y) + }) } func TestDivUint256(t *testing.T) { diff --git a/examples/gno.land/p/demo/int256/conversion.gno b/examples/gno.land/p/demo/int256/conversion.gno index ee6e7560f15..9e264e7e46b 100644 --- a/examples/gno.land/p/demo/int256/conversion.gno +++ b/examples/gno.land/p/demo/int256/conversion.gno @@ -82,5 +82,6 @@ func (z *Int) ToString() string { if z.neg { return "-" + t } + return t } diff --git a/examples/gno.land/p/demo/int256/conversion_test.gno b/examples/gno.land/p/demo/int256/conversion_test.gno index da54c226669..b085a77a15a 100644 --- a/examples/gno.land/p/demo/int256/conversion_test.gno +++ b/examples/gno.land/p/demo/int256/conversion_test.gno @@ -169,3 +169,66 @@ func TestSetUint256(t *testing.T) { } } } + +func TestToString(t *testing.T) { + tests := []struct { + name string + setup func() *Int + expected string + }{ + { + name: "Zero from subtraction", + setup: func() *Int { + minusThree := MustFromDecimal("-3") + three := MustFromDecimal("3") + return Zero().Add(minusThree, three) + }, + expected: "0", + }, + { + name: "Zero from right shift", + setup: func() *Int { + return Zero().Rsh(One(), 1234) + }, + expected: "0", + }, + { + name: "Positive number", + setup: func() *Int { + return MustFromDecimal("42") + }, + expected: "42", + }, + { + name: "Negative number", + setup: func() *Int { + return MustFromDecimal("-42") + }, + expected: "-42", + }, + { + name: "Large positive number", + setup: func() *Int { + return MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + }, + expected: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + { + name: "Large negative number", + setup: func() *Int { + return MustFromDecimal("-115792089237316195423570985008687907853269984665640564039457584007913129639935") + }, + expected: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := tt.setup() + result := z.ToString() + if result != tt.expected { + t.Errorf("ToString() = %s, want %s", result, tt.expected) + } + }) + } +} 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 fbfcfec8ae3..95065b8cd64 100644 --- a/examples/gno.land/p/demo/memeland/memeland_test.gno +++ b/examples/gno.land/p/demo/memeland/memeland_test.gno @@ -6,17 +6,15 @@ import ( "testing" "time" - "gno.land/p/demo/seqid" "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) { @@ -57,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) }) } } @@ -92,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 seqid.ID(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)) } } @@ -152,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 seqid.ID(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) { @@ -226,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) { @@ -240,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) { @@ -278,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.gno b/examples/gno.land/p/demo/merkle/merkle.gno index 54b878bffb1..f4fcc4dad40 100644 --- a/examples/gno.land/p/demo/merkle/merkle.gno +++ b/examples/gno.land/p/demo/merkle/merkle.gno @@ -19,6 +19,17 @@ type Node struct { position uint8 } +func NewNode(hash []byte, position uint8) Node { + return Node{ + hash: hash, + position: position, + } +} + +func (n Node) Position() uint8 { + return n.position +} + func (n Node) Hash() string { return hex.EncodeToString(n.hash[:]) } 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/errors.gno b/examples/gno.land/p/demo/ownable/errors.gno index ffbf6ab3f6f..89776a6cf12 100644 --- a/examples/gno.land/p/demo/ownable/errors.gno +++ b/examples/gno.land/p/demo/ownable/errors.gno @@ -3,6 +3,6 @@ package ownable import "errors" var ( - ErrUnauthorized = errors.New("unauthorized; caller is not owner") - ErrInvalidAddress = errors.New("new owner address is invalid") + ErrUnauthorized = errors.New("ownable: caller is not owner") + ErrInvalidAddress = errors.New("ownable: new owner address is invalid") ) diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno new file mode 100644 index 00000000000..f9f0ea15dd9 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno @@ -0,0 +1,90 @@ +// Package authorizable is an extension of p/demo/ownable; +// It allows the user to instantiate an Authorizable struct, which extends +// p/demo/ownable with a list of users that are authorized for something. +// By using authorizable, you have a superuser (ownable), as well as another +// authorization level, which can be used for adding moderators or similar to your realm. +package authorizable + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" +) + +type Authorizable struct { + *ownable.Ownable // owner in ownable is superuser + authorized *avl.Tree // std.Addr > struct{}{} +} + +func NewAuthorizable() *Authorizable { + a := &Authorizable{ + ownable.New(), + avl.NewTree(), + } + + // Add owner to auth list + a.authorized.Set(a.Owner().String(), struct{}{}) + return a +} + +func NewAuthorizableWithAddress(addr std.Address) *Authorizable { + a := &Authorizable{ + ownable.NewWithAddress(addr), + avl.NewTree(), + } + + // Add owner to auth list + a.authorized.Set(a.Owner().String(), struct{}{}) + return a +} + +func (a *Authorizable) AddToAuthList(addr std.Address) error { + if err := a.CallerIsOwner(); err != nil { + return ErrNotSuperuser + } + + if _, exists := a.authorized.Get(addr.String()); exists { + return ErrAlreadyInList + } + + a.authorized.Set(addr.String(), struct{}{}) + + return nil +} + +func (a *Authorizable) DeleteFromAuthList(addr std.Address) error { + if err := a.CallerIsOwner(); err != nil { + return ErrNotSuperuser + } + + if !a.authorized.Has(addr.String()) { + return ErrNotInAuthList + } + + if _, removed := a.authorized.Remove(addr.String()); !removed { + str := ufmt.Sprintf("authorizable: could not remove %s from auth list", addr.String()) + panic(str) + } + + return nil +} + +func (a Authorizable) CallerOnAuthList() error { + caller := std.PrevRealm().Addr() + + if !a.authorized.Has(caller.String()) { + return ErrNotInAuthList + } + + return nil +} + +func (a Authorizable) AssertOnAuthList() { + caller := std.PrevRealm().Addr() + + if !a.authorized.Has(caller.String()) { + panic(ErrNotInAuthList) + } +} diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno new file mode 100644 index 00000000000..10a5e411bdb --- /dev/null +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno @@ -0,0 +1,116 @@ +package authorizable + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") + charlie = testutils.TestAddress("charlie") +) + +func TestNewAuthorizable(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // TODO(bug, issue #2371): should not be needed + + a := NewAuthorizable() + got := a.Owner() + + if alice != got { + t.Fatalf("Expected %s, got: %s", alice, got) + } +} + +func TestNewAuthorizableWithAddress(t *testing.T) { + a := NewAuthorizableWithAddress(alice) + + got := a.Owner() + + if alice != got { + t.Fatalf("Expected %s, got: %s", alice, got) + } +} + +func TestCallerOnAuthList(t *testing.T) { + a := NewAuthorizableWithAddress(alice) + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + if err := a.CallerOnAuthList(); err == ErrNotInAuthList { + t.Fatalf("expected alice to be on the list") + } +} + +func TestNotCallerOnAuthList(t *testing.T) { + a := NewAuthorizableWithAddress(alice) + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + if err := a.CallerOnAuthList(); err == nil { + t.Fatalf("expected bob to not be on the list") + } +} + +func TestAddToAuthList(t *testing.T) { + a := NewAuthorizableWithAddress(alice) + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + if err := a.AddToAuthList(bob); err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + if err := a.AddToAuthList(bob); err == nil { + t.Fatalf("Expected AddToAuth to error while bob called it, but it didn't") + } +} + +func TestDeleteFromList(t *testing.T) { + a := NewAuthorizableWithAddress(alice) + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + if err := a.AddToAuthList(bob); err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if err := a.AddToAuthList(charlie); err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + // Try an unauthorized deletion + if err := a.DeleteFromAuthList(alice); err == nil { + t.Fatalf("Expected DelFromAuth to error with %v", err) + } + + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + if err := a.DeleteFromAuthList(charlie); err != nil { + t.Fatalf("Expected no error, got %v", err) + } +} + +func TestAssertOnList(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + a := NewAuthorizableWithAddress(alice) + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + uassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() { + a.AssertOnAuthList() + }) +} diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/errors.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/errors.gno new file mode 100644 index 00000000000..4ba5983bccb --- /dev/null +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/errors.gno @@ -0,0 +1,9 @@ +package authorizable + +import "errors" + +var ( + ErrNotInAuthList = errors.New("authorizable: caller is not in authorized list") + ErrNotSuperuser = errors.New("authorizable: caller is not superuser") + ErrAlreadyInList = errors.New("authorizable: address is already in authorized list") +) diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod b/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod new file mode 100644 index 00000000000..f36823f3f71 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/ownable/exts/authorizable + +require ( + gno.land/p/demo/avl 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 +) diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index 75ebcde0a28..a77b22461a9 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -1,8 +1,6 @@ package ownable -import ( - "std" -) +import "std" const OwnershipTransferEvent = "OwnershipTransfer" @@ -19,7 +17,9 @@ func New() *Ownable { } func NewWithAddress(addr std.Address) *Ownable { - return &Ownable{owner: addr} + return &Ownable{ + owner: addr, + } } // TransferOwnership transfers ownership of the Ownable struct to a new address @@ -40,6 +40,7 @@ func (o *Ownable) TransferOwnership(newOwner std.Address) error { "from", string(prevOwner), "to", string(newOwner), ) + return nil } @@ -64,6 +65,7 @@ func (o *Ownable) DropOwnership() error { return nil } +// Owner returns the owner address from Ownable func (o Ownable) Owner() std.Address { return o.owner } @@ -73,9 +75,11 @@ func (o Ownable) CallerIsOwner() error { if std.PrevRealm().Addr() == o.owner { return nil } + return ErrUnauthorized } +// AssertCallerIsOwner panics if the caller is not the 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 6217948d587..a9d97154f45 100644 --- a/examples/gno.land/p/demo/ownable/ownable_test.gno +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -9,52 +9,60 @@ import ( ) var ( - firstCaller = testutils.TestAddress("first") - secondCaller = testutils.TestAddress("second") + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") ) func TestNew(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) - std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // TODO(bug): should not be needed o := New() got := o.Owner() - uassert.Equal(t, firstCaller, got) + if alice != got { + t.Fatalf("Expected %s, got: %s", alice, got) + } } func TestNewWithAddress(t *testing.T) { - o := NewWithAddress(firstCaller) + o := NewWithAddress(alice) got := o.Owner() - uassert.Equal(t, firstCaller, got) + if alice != got { + t.Fatalf("Expected %s, got: %s", alice, got) + } } func TestOwner(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetRealm(std.NewUserRealm(alice)) o := New() - expected := firstCaller + expected := alice got := o.Owner() uassert.Equal(t, expected, got) } func TestTransferOwnership(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetRealm(std.NewUserRealm(alice)) o := New() - err := o.TransferOwnership(secondCaller) - uassert.NoError(t, err, "TransferOwnership failed") + err := o.TransferOwnership(bob) + if err != nil { + t.Fatalf("TransferOwnership failed, %v", err) + } got := o.Owner() - uassert.Equal(t, secondCaller, got) + if bob != got { + t.Fatalf("Expected: %s, got: %s", bob, got) + } } func TestCallerIsOwner(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetRealm(std.NewUserRealm(alice)) o := New() - unauthorizedCaller := secondCaller + unauthorizedCaller := bob std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) std.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed @@ -64,7 +72,7 @@ func TestCallerIsOwner(t *testing.T) { } func TestDropOwnership(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetRealm(std.NewUserRealm(alice)) o := New() @@ -78,23 +86,25 @@ func TestDropOwnership(t *testing.T) { // Errors func TestErrUnauthorized(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) - std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // TODO(bug): should not be needed o := New() - std.TestSetRealm(std.NewUserRealm(secondCaller)) - std.TestSetOrigCaller(secondCaller) // TODO(bug): should not be needed + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) // TODO(bug): should not be needed - err := o.TransferOwnership(firstCaller) - uassert.ErrorContains(t, err, ErrUnauthorized.Error()) + err := o.TransferOwnership(alice) + if err != ErrUnauthorized { + t.Fatalf("Should've been ErrUnauthorized, was %v", err) + } err = o.DropOwnership() uassert.ErrorContains(t, err, ErrUnauthorized.Error()) } func TestErrInvalidAddress(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetRealm(std.NewUserRealm(alice)) o := New() 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/subscription/doc.gno b/examples/gno.land/p/demo/subscription/doc.gno new file mode 100644 index 00000000000..9cc102fcc9a --- /dev/null +++ b/examples/gno.land/p/demo/subscription/doc.gno @@ -0,0 +1,66 @@ +// Package subscription provides a flexible system for managing both recurring and +// lifetime subscriptions in Gno applications. It enables developers to handle +// payment-based access control for services or products. The library supports +// both subscriptions requiring periodic payments (recurring) and one-time payments +// (lifetime). Subscriptions are tracked using an AVL tree for efficient management +// of subscription statuses. +// +// Usage: +// +// Import the required sub-packages (`recurring` and/or `lifetime`) to manage specific +// subscription types. The methods provided allow users to subscribe, check subscription +// status, and manage payments. +// +// Recurring Subscription: +// +// Recurring subscriptions require periodic payments to maintain access. +// Users pay to extend their access for a specific duration. +// +// Example: +// +// // Create a recurring subscription requiring 100 ugnot every 30 days +// recSub := recurring.NewRecurringSubscription(time.Hour * 24 * 30, 100) +// +// // Process payment for the recurring subscription +// recSub.Subscribe() +// +// // Gift a recurring subscription to another user +// recSub.GiftSubscription(recipientAddress) +// +// // Check if a user has a valid subscription +// recSub.HasValidSubscription(addr) +// +// // Get the expiration date of the subscription +// recSub.GetExpiration(caller) +// +// // Update the subscription amount to 200 ugnot +// recSub.UpdateAmount(200) +// +// // Get the current subscription amount +// recSub.GetAmount() +// +// Lifetime Subscription: +// +// Lifetime subscriptions require a one-time payment for permanent access. +// Once paid, users have indefinite access without further payments. +// +// Example: +// +// // Create a lifetime subscription costing 500 ugnot +// lifeSub := lifetime.NewLifetimeSubscription(500) +// +// // Process payment for lifetime access +// lifeSub.Subscribe() +// +// // Gift a lifetime subscription to another user +// lifeSub.GiftSubscription(recipientAddress) +// +// // Check if a user has a valid subscription +// lifeSub.HasValidSubscription(addr) +// +// // Update the lifetime subscription amount to 1000 ugnot +// lifeSub.UpdateAmount(1000) +// +// // Get the current lifetime subscription amount +// lifeSub.GetAmount() +package subscription diff --git a/examples/gno.land/p/demo/subscription/gno.mod b/examples/gno.land/p/demo/subscription/gno.mod new file mode 100644 index 00000000000..ea60a4c628a --- /dev/null +++ b/examples/gno.land/p/demo/subscription/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/subscription diff --git a/examples/gno.land/p/demo/subscription/lifetime/errors.gno b/examples/gno.land/p/demo/subscription/lifetime/errors.gno new file mode 100644 index 00000000000..faeda4cd9fe --- /dev/null +++ b/examples/gno.land/p/demo/subscription/lifetime/errors.gno @@ -0,0 +1,10 @@ +package lifetime + +import "errors" + +var ( + ErrNoSub = errors.New("lifetime subscription: no active subscription found") + ErrAmt = errors.New("lifetime subscription: payment amount does not match the required subscription amount") + ErrAlreadySub = errors.New("lifetime subscription: this address already has an active lifetime subscription") + ErrNotAuthorized = errors.New("lifetime subscription: action not authorized") +) diff --git a/examples/gno.land/p/demo/subscription/lifetime/gno.mod b/examples/gno.land/p/demo/subscription/lifetime/gno.mod new file mode 100644 index 00000000000..0084aa714c5 --- /dev/null +++ b/examples/gno.land/p/demo/subscription/lifetime/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/subscription/lifetime + +require ( + gno.land/p/demo/avl 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 +) diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno new file mode 100644 index 00000000000..8a4c10b687b --- /dev/null +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno @@ -0,0 +1,81 @@ +package lifetime + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" +) + +// LifetimeSubscription represents a subscription that requires only a one-time payment. +// It grants permanent access to a service or product. +type LifetimeSubscription struct { + ownable.Ownable + amount int64 + subs *avl.Tree // std.Address -> bool +} + +// NewLifetimeSubscription creates and returns a new lifetime subscription. +func NewLifetimeSubscription(amount int64) *LifetimeSubscription { + return &LifetimeSubscription{ + Ownable: *ownable.New(), + amount: amount, + subs: avl.NewTree(), + } +} + +// processSubscription handles the subscription process for a given receiver. +func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error { + amount := std.GetOrigSend() + + if amount.AmountOf("ugnot") != ls.amount { + return ErrAmt + } + + _, exists := ls.subs.Get(receiver.String()) + + if exists { + return ErrAlreadySub + } + + ls.subs.Set(receiver.String(), true) + + return nil +} + +// Subscribe processes the payment for a lifetime subscription. +func (ls *LifetimeSubscription) Subscribe() error { + caller := std.PrevRealm().Addr() + return ls.processSubscription(caller) +} + +// GiftSubscription allows the caller to pay for a lifetime subscription for another user. +func (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error { + return ls.processSubscription(receiver) +} + +// HasValidSubscription checks if the given address has an active lifetime subscription. +func (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error { + _, exists := ls.subs.Get(addr.String()) + + if !exists { + return ErrNoSub + } + + return nil +} + +// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price. +func (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error { + if err := ls.CallerIsOwner(); err != nil { + return ErrNotAuthorized + } + + ls.amount = newAmount + return nil +} + +// GetAmount returns the current subscription price. +func (ls *LifetimeSubscription) GetAmount() int64 { + return ls.amount +} diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno new file mode 100644 index 00000000000..efbae90c11c --- /dev/null +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno @@ -0,0 +1,105 @@ +package lifetime + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") + charlie = testutils.TestAddress("charlie") +) + +func TestLifetimeSubscription(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := ls.Subscribe() + uassert.NoError(t, err, "Expected ProcessPayment to succeed") + + err = ls.HasValidSubscription(std.PrevRealm().Addr()) + uassert.NoError(t, err, "Expected Alice to have access") +} + +func TestLifetimeSubscriptionGift(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := ls.GiftSubscription(bob) + uassert.NoError(t, err, "Expected ProcessPaymentGift to succeed for Bob") + + err = ls.HasValidSubscription(bob) + uassert.NoError(t, err, "Expected Bob to have access") + + err = ls.HasValidSubscription(charlie) + uassert.Error(t, err, "Expected Charlie to fail access check") +} + +func TestUpdateAmountAuthorization(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + err := ls.UpdateAmount(2000) + uassert.NoError(t, err, "Expected Alice to succeed in updating amount") + + std.TestSetOrigCaller(bob) + + err = ls.UpdateAmount(3000) + uassert.Error(t, err, "Expected Bob to fail when updating amount") +} + +func TestIncorrectPaymentAmount(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + err := ls.Subscribe() + uassert.Error(t, err, "Expected payment to fail with incorrect amount") +} + +func TestMultipleSubscriptionAttempts(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := ls.Subscribe() + uassert.NoError(t, err, "Expected first subscription to succeed") + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err = ls.Subscribe() + uassert.Error(t, err, "Expected second subscription to fail as Alice is already subscribed") +} + +func TestGiftSubscriptionWithIncorrectAmount(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + err := ls.GiftSubscription(bob) + uassert.Error(t, err, "Expected gift subscription to fail with incorrect amount") + + err = ls.HasValidSubscription(bob) + uassert.Error(t, err, "Expected Bob to not have access after incorrect gift subscription") +} + +func TestUpdateAmountEffectiveness(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + ls := NewLifetimeSubscription(1000) + + err := ls.UpdateAmount(2000) + uassert.NoError(t, err, "Expected Alice to succeed in updating amount") + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err = ls.Subscribe() + uassert.Error(t, err, "Expected subscription to fail with old amount after update") + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 2000}}, nil) + err = ls.Subscribe() + uassert.NoError(t, err, "Expected subscription to succeed with new amount") +} diff --git a/examples/gno.land/p/demo/subscription/recurring/errors.gno b/examples/gno.land/p/demo/subscription/recurring/errors.gno new file mode 100644 index 00000000000..76a55e069bf --- /dev/null +++ b/examples/gno.land/p/demo/subscription/recurring/errors.gno @@ -0,0 +1,11 @@ +package recurring + +import "errors" + +var ( + ErrNoSub = errors.New("recurring subscription: no active subscription found") + ErrSubExpired = errors.New("recurring subscription: your subscription has expired") + ErrAmt = errors.New("recurring subscription: payment amount does not match the required subscription amount") + ErrAlreadySub = errors.New("recurring subscription: this address already has an active subscription") + ErrNotAuthorized = errors.New("recurring subscription: action not authorized") +) diff --git a/examples/gno.land/p/demo/subscription/recurring/gno.mod b/examples/gno.land/p/demo/subscription/recurring/gno.mod new file mode 100644 index 00000000000..d3cf8a044f8 --- /dev/null +++ b/examples/gno.land/p/demo/subscription/recurring/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/subscription/recurring + +require ( + gno.land/p/demo/avl 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 +) diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring.gno b/examples/gno.land/p/demo/subscription/recurring/recurring.gno new file mode 100644 index 00000000000..b5277bd716e --- /dev/null +++ b/examples/gno.land/p/demo/subscription/recurring/recurring.gno @@ -0,0 +1,104 @@ +package recurring + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" +) + +// RecurringSubscription represents a subscription that requires periodic payments. +// It includes the duration of the subscription and the amount required per period. +type RecurringSubscription struct { + ownable.Ownable + duration time.Duration + amount int64 + subs *avl.Tree // std.Address -> time.Time +} + +// NewRecurringSubscription creates and returns a new recurring subscription. +func NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription { + return &RecurringSubscription{ + Ownable: *ownable.New(), + duration: duration, + amount: amount, + subs: avl.NewTree(), + } +} + +// HasValidSubscription verifies if the caller has an active recurring subscription. +func (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error { + expTime, exists := rs.subs.Get(addr.String()) + if !exists { + return ErrNoSub + } + + if time.Now().After(expTime.(time.Time)) { + return ErrSubExpired + } + + return nil +} + +// processSubscription processes the payment for a given receiver and renews or adds their subscription. +func (rs *RecurringSubscription) processSubscription(receiver std.Address) error { + amount := std.GetOrigSend() + + if amount.AmountOf("ugnot") != rs.amount { + return ErrAmt + } + + expTime, exists := rs.subs.Get(receiver.String()) + + // If the user is already a subscriber but his subscription has expired, authorize renewal + if exists { + expiration := expTime.(time.Time) + if time.Now().Before(expiration) { + return ErrAlreadySub + } + } + + // Renew or add subscription + newExpiration := time.Now().Add(rs.duration) + rs.subs.Set(receiver.String(), newExpiration) + + return nil +} + +// Subscribe handles the payment for the caller's subscription. +func (rs *RecurringSubscription) Subscribe() error { + caller := std.PrevRealm().Addr() + + return rs.processSubscription(caller) +} + +// GiftSubscription allows the user to pay for a subscription for another user (receiver). +func (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error { + return rs.processSubscription(receiver) +} + +// GetExpiration returns the expiration date of the recurring subscription for a given caller. +func (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) { + expTime, exists := rs.subs.Get(addr.String()) + if !exists { + return time.Time{}, ErrNoSub + } + + return expTime.(time.Time), nil +} + +// UpdateAmount allows the owner of the subscription contract to change the required subscription amount. +func (rs *RecurringSubscription) UpdateAmount(newAmount int64) error { + if err := rs.CallerIsOwner(); err != nil { + return ErrNotAuthorized + } + + rs.amount = newAmount + return nil +} + +// GetAmount returns the current amount required for each subscription period. +func (rs *RecurringSubscription) GetAmount() int64 { + return rs.amount +} diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno new file mode 100644 index 00000000000..e8bca15c0bf --- /dev/null +++ b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno @@ -0,0 +1,134 @@ +package recurring + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") + charlie = testutils.TestAddress("charlie") +) + +func TestRecurringSubscription(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour*24, 1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := rs.Subscribe() + uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") + + err = rs.HasValidSubscription(std.PrevRealm().Addr()) + uassert.NoError(t, err, "Expected Alice to have access") + + expiration, err := rs.GetExpiration(std.PrevRealm().Addr()) + uassert.NoError(t, err, "Expected to get expiration for Alice") +} + +func TestRecurringSubscriptionGift(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour*24, 1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := rs.GiftSubscription(bob) + uassert.NoError(t, err, "Expected ProcessPaymentGift to succeed for Bob") + + err = rs.HasValidSubscription(bob) + uassert.NoError(t, err, "Expected Bob to have access") + + err = rs.HasValidSubscription(charlie) + uassert.Error(t, err, "Expected Charlie to fail access check") +} + +func TestRecurringSubscriptionExpiration(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour, 1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := rs.Subscribe() + uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") + + err = rs.HasValidSubscription(std.PrevRealm().Addr()) + uassert.NoError(t, err, "Expected Alice to have access") + + expiration := time.Now().Add(-time.Hour * 2) + rs.subs.Set(std.PrevRealm().Addr().String(), expiration) + + err = rs.HasValidSubscription(std.PrevRealm().Addr()) + uassert.Error(t, err, "Expected Alice's subscription to be expired") +} + +func TestUpdateAmountAuthorization(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour*24, 1000) + + err := rs.UpdateAmount(2000) + uassert.NoError(t, err, "Expected Alice to succeed in updating amount") + + std.TestSetOrigCaller(bob) + err = rs.UpdateAmount(3000) + uassert.Error(t, err, "Expected Bob to fail when updating amount") +} + +func TestGetAmount(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour*24, 1000) + + amount := rs.GetAmount() + uassert.Equal(t, amount, int64(1000), "Expected the initial amount to be 1000 ugnot") + + err := rs.UpdateAmount(2000) + uassert.NoError(t, err, "Expected Alice to succeed in updating amount") + + amount = rs.GetAmount() + uassert.Equal(t, amount, int64(2000), "Expected the updated amount to be 2000 ugnot") +} + +func TestIncorrectPaymentAmount(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour*24, 1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + err := rs.Subscribe() + uassert.Error(t, err, "Expected payment with incorrect amount to fail") +} + +func TestMultiplePaymentsForSameUser(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour*24, 1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := rs.Subscribe() + uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err = rs.Subscribe() + uassert.Error(t, err, "Expected second ProcessPayment to fail for Alice due to existing subscription") +} + +func TestRecurringSubscriptionWithMultiplePayments(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + rs := NewRecurringSubscription(time.Hour, 1000) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err := rs.Subscribe() + uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") + + err = rs.HasValidSubscription(std.PrevRealm().Addr()) + uassert.NoError(t, err, "Expected Alice to have access after first payment") + + expiration := time.Now().Add(-time.Hour * 2) + rs.subs.Set(std.PrevRealm().Addr().String(), expiration) + + std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + err = rs.Subscribe() + uassert.NoError(t, err, "Expected second ProcessPayment to succeed for Alice") + + err = rs.HasValidSubscription(std.PrevRealm().Addr()) + uassert.NoError(t, err, "Expected Alice to have access after second payment") +} diff --git a/examples/gno.land/p/demo/subscription/subscription.gno b/examples/gno.land/p/demo/subscription/subscription.gno new file mode 100644 index 00000000000..cc52a2c0e2d --- /dev/null +++ b/examples/gno.land/p/demo/subscription/subscription.gno @@ -0,0 +1,12 @@ +package subscription + +import ( + "std" +) + +// Subscription interface defines standard methods that all subscription types must implement. +type Subscription interface { + HasValidSubscription(std.Address) error + Subscribe() error + UpdateAmount(newAmount int64) error +} 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/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/gno.mod b/examples/gno.land/p/demo/uassert/gno.mod index a70e7db825d..f22276564bf 100644 --- a/examples/gno.land/p/demo/uassert/gno.mod +++ b/examples/gno.land/p/demo/uassert/gno.mod @@ -1 +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/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno index 0bae30f0730..2776e93dca9 100644 --- a/examples/gno.land/p/demo/uassert/uassert.gno +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -5,6 +5,8 @@ import ( "std" "strconv" "strings" + + "gno.land/p/demo/diff" ) // NoError asserts that a function returned no error (i.e. `nil`). @@ -127,6 +129,10 @@ func Equal(t TestingT, expected, actual interface{}, msgs ...string) bool { 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 { @@ -239,24 +245,219 @@ func Equal(t TestingT, expected, actual interface{}, msgs ...string) bool { return true } -func Empty(t TestingT, obj interface{}, msgs ...string) bool { +// NotEqual asserts that two objects are not equal. +func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool { t.Helper() - switch val := obj.(type) { + + 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 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) + if av, ok := actual.(string); ok { + notEqual = ev != av + ok_ = true + es, as = ev, as } case std.Address: - var zeroAddr std.Address - if val != zeroAddr { - return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val)) + 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.Empty: unsupported type") + 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 isNumberEmpty(n interface{}) (isNumber, isEmpty bool) { + switch n := n.(type) { + // NOTE: the cases are split individually, so that n becomes of the + // asserted type; the type of '0' was correctly inferred and converted + // to the corresponding type, int, int8, etc. + case int: + return true, n == 0 + case int8: + return true, n == 0 + case int16: + return true, n == 0 + case int32: + return true, n == 0 + case int64: + return true, n == 0 + case uint: + return true, n == 0 + case uint8: + return true, n == 0 + case uint16: + return true, n == 0 + case uint32: + return true, n == 0 + case uint64: + return true, n == 0 + case float32: + return true, n == 0 + case float64: + return true, n == 0 + } + return false, false +} +func Empty(t TestingT, obj interface{}, msgs ...string) bool { + t.Helper() + + isNumber, isEmpty := isNumberEmpty(obj) + if isNumber { + if !isEmpty { + return fail(t, msgs, "uassert.Empty: not empty number: %d", obj) + } + } else { + switch val := obj.(type) { + case string: + if val != "" { + return fail(t, msgs, "uassert.Empty: not empty string: %s", 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() + isNumber, isEmpty := isNumberEmpty(obj) + if isNumber { + if isEmpty { + return fail(t, msgs, "uassert.NotEmpty: empty number: %d", obj) + } + } else { + switch val := obj.(type) { + case string: + if val == "" { + return fail(t, msgs, "uassert.NotEmpty: empty string: %s", 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 index bd1194c4dff..7862eca7305 100644 --- a/examples/gno.land/p/demo/uassert/uassert_test.gno +++ b/examples/gno.land/p/demo/uassert/uassert_test.gno @@ -157,6 +157,51 @@ func TestEqual(t *testing.T) { } } +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 @@ -173,6 +218,7 @@ func TestEmpty(t *testing.T) { {"", true}, {0, true}, {int(0), true}, + {int32(0), true}, {int64(0), true}, {uint(0), true}, // XXX: continue @@ -201,3 +247,121 @@ func TestEmpty(t *testing.T) { }) } } + +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}, + {int32(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/urequire/urequire.gno b/examples/gno.land/p/demo/urequire/urequire.gno index 810d32649f9..94721dd85a7 100644 --- a/examples/gno.land/p/demo/urequire/urequire.gno +++ b/examples/gno.land/p/demo/urequire/urequire.gno @@ -78,6 +78,14 @@ func Equal(t uassert.TestingT, expected, actual interface{}, msgs ...string) { 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...) { @@ -85,3 +93,11 @@ func Empty(t uassert.TestingT, obj interface{}, msgs ...string) { } 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/moul/printfdebugging/color.gno b/examples/gno.land/p/moul/printfdebugging/color.gno new file mode 100644 index 00000000000..b3bf647b9b5 --- /dev/null +++ b/examples/gno.land/p/moul/printfdebugging/color.gno @@ -0,0 +1,81 @@ +package printfdebugging + +// consts copied from https://github.com/fatih/color/blob/main/color.go + +// Attribute defines a single SGR Code +type Attribute int + +const Escape = "\x1b" + +// Base attributes +const ( + Reset Attribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +const ( + ResetBold Attribute = iota + 22 + ResetItalic + ResetUnderline + ResetBlinking + _ + ResetReversed + ResetConcealed + ResetCrossedOut +) + +// Foreground text colors +const ( + FgBlack Attribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack Attribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack Attribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack Attribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) diff --git a/examples/gno.land/p/moul/printfdebugging/gno.mod b/examples/gno.land/p/moul/printfdebugging/gno.mod new file mode 100644 index 00000000000..2cf6aa09e61 --- /dev/null +++ b/examples/gno.land/p/moul/printfdebugging/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/printfdebugging + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/moul/printfdebugging/printfdebugging.gno b/examples/gno.land/p/moul/printfdebugging/printfdebugging.gno new file mode 100644 index 00000000000..a12a3dfadd2 --- /dev/null +++ b/examples/gno.land/p/moul/printfdebugging/printfdebugging.gno @@ -0,0 +1,19 @@ +// this package is a joke... or not. +package printfdebugging + +import ( + "strings" + + "gno.land/p/demo/ufmt" +) + +func BigRedLine(args ...string) { + println(ufmt.Sprintf("%s[%dm####################################%s[%dm %s", + Escape, int(BgRed), Escape, int(Reset), + strings.Join(args, " "), + )) +} + +func Success() { + println(" \033[31mS\033[33mU\033[32mC\033[36mC\033[34mE\033[35mS\033[31mS\033[0m ") +} diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index f2d3ddebadc..072c98f3bd6 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,7 @@ module gno.land/r/demo/art/gnoface -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/entropy v0.0.0-latest + gno.land/p/demo/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 9e85c5c7387..b4bc8e222e5 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -2,15 +2,15 @@ package gnoface import ( "math/rand" - "std" "strconv" "strings" + "gno.land/p/demo/entropy" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := uint64(std.GetHeight()) + seed := uint64(entropy.New().Value()) path = strings.TrimSpace(path) if path != "" { diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno index e82bd819483..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,6 +3,7 @@ package gnoface import ( "testing" + "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" ) @@ -64,9 +65,7 @@ D| x X |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) }) } } @@ -128,9 +127,7 @@ D| x X |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 414941b947b..446c76e5d12 100644 --- a/examples/gno.land/r/demo/art/millipede/millipede.gno +++ b/examples/gno.land/r/demo/art/millipede/millipede.gno @@ -40,7 +40,7 @@ func Render(path string) string { output := "```\n" + Draw(size) + "```\n" if size > minSize { - output += ufmt.Sprintf("[%d](/r/demo/art/millpede:%d)< ", size-1, size-1) + output += ufmt.Sprintf("[%d](/r/demo/art/millipede:%d)< ", size-1, size-1) } if size < maxSize { output += ufmt.Sprintf(" >[%d](/r/demo/art/millipede:%d)", size+1, size+1) 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 8455bb9962e..035b611d881 100644 --- a/examples/gno.land/r/demo/art/millipede/millipede_test.gno +++ b/examples/gno.land/r/demo/art/millipede/millipede_test.gno @@ -1,6 +1,10 @@ package millipede -import "testing" +import ( + "testing" + + "gno.land/p/demo/uassert" +) func TestRender(t *testing.T) { cases := []struct { @@ -31,7 +35,7 @@ func TestRender(t *testing.T) { ╚═(███)═╝ ╚═(███)═╝ ╚═(███)═╝ -` + "```\n[19](/r/demo/art/millpede:19)< >[21](/r/demo/art/millipede:21)", +` + "```\n[19](/r/demo/art/millipede:19)< >[21](/r/demo/art/millipede:21)", }, { path: "4", @@ -41,16 +45,14 @@ func TestRender(t *testing.T) { ╚═(███)═╝ ╚═(███)═╝ ╚═(███)═╝ -` + "```\n[3](/r/demo/art/millpede:3)< >[5](/r/demo/art/millipede:5)", +` + "```\n[3](/r/demo/art/millipede: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/boards/README.md b/examples/gno.land/r/demo/boards/README.md index a9b68ec9c92..628bc9aa349 100644 --- a/examples/gno.land/r/demo/boards/README.md +++ b/examples/gno.land/r/demo/boards/README.md @@ -58,7 +58,8 @@ 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/contribs/gnofaucet/README.md + ### Start the `gnoland` node. 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 5405a2508ec..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/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_filetest.gno b/examples/gno.land/r/demo/boards/z_0_filetest.gno index 1debeecba04..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/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_b_filetest.gno b/examples/gno.land/r/demo/boards/z_10_b_filetest.gno index 58ecbecabdf..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/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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 50bcfac355c..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/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 9a067f5aac5..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/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 524b626ff6d..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/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 33e6ca1eea9..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/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 9e85b8ace8c..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/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" 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_7_filetest.gno b/examples/gno.land/r/demo/boards/z_7_filetest.gno index 7a6b4d89eec..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/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 e7f94a78746..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/demo/boards_test package boards_test -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" diff --git a/examples/gno.land/r/demo/counter/counter.gno b/examples/gno.land/r/demo/counter/counter.gno new file mode 100644 index 00000000000..43943e114dc --- /dev/null +++ b/examples/gno.land/r/demo/counter/counter.gno @@ -0,0 +1,14 @@ +package counter + +import "strconv" + +var counter int + +func Increment() int { + counter++ + return counter +} + +func Render(_ string) string { + return strconv.Itoa(counter) +} diff --git a/examples/gno.land/r/demo/counter/counter_test.gno b/examples/gno.land/r/demo/counter/counter_test.gno new file mode 100644 index 00000000000..352889f7e59 --- /dev/null +++ b/examples/gno.land/r/demo/counter/counter_test.gno @@ -0,0 +1,22 @@ +package counter + +import "testing" + +func TestIncrement(t *testing.T) { + counter = 0 + val := Increment() + if val != 1 { + t.Fatalf("result from Increment(): %d != 1", val) + } + if counter != val { + t.Fatalf("counter (%d) != val (%d)", counter, val) + } +} + +func TestRender(t *testing.T) { + counter = 1337 + res := Render("") + if res != "1337" { + t.Fatalf("render result %q != %q", res, "1337") + } +} diff --git a/examples/gno.land/r/demo/counter/gno.mod b/examples/gno.land/r/demo/counter/gno.mod new file mode 100644 index 00000000000..332d4e6da6a --- /dev/null +++ b/examples/gno.land/r/demo/counter/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/counter diff --git a/examples/gno.land/r/demo/disperse/disperse.gno b/examples/gno.land/r/demo/disperse/disperse.gno new file mode 100644 index 00000000000..0dc833dda95 --- /dev/null +++ b/examples/gno.land/r/demo/disperse/disperse.gno @@ -0,0 +1,99 @@ +package disperse + +import ( + "std" + + tokens "gno.land/r/demo/grc20factory" +) + +// Get address of Disperse realm +var realmAddr = std.CurrentRealm().Addr() + +// DisperseUgnot parses receivers and amounts and sends out ugnot +// The function will send out the coins to the addresses and return the leftover coins to the caller +// if there are any to return +func DisperseUgnot(addresses []std.Address, coins std.Coins) { + coinSent := std.GetOrigSend() + caller := std.PrevRealm().Addr() + banker := std.GetBanker(std.BankerTypeOrigSend) + + if len(addresses) != len(coins) { + panic(ErrNumAddrValMismatch) + } + + for _, coin := range coins { + if coin.Amount <= 0 { + panic(ErrNegativeCoinAmount) + } + + if banker.GetCoins(realmAddr).AmountOf(coin.Denom) < coin.Amount { + panic(ErrMismatchBetweenSentAndParams) + } + } + + // Send coins + for i, _ := range addresses { + banker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i])) + } + + // Return possible leftover coins + for _, coin := range coinSent { + leftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom) + if leftoverAmt > 0 { + send := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)} + banker.SendCoins(realmAddr, caller, send) + } + } +} + +// DisperseGRC20 disperses tokens to multiple addresses +// Note that it is necessary to approve the realm to spend the tokens before calling this function +// see the corresponding filetests for examples +func DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) { + caller := std.PrevRealm().Addr() + + if (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) { + panic(ErrArgLenAndSentLenMismatch) + } + + for i := 0; i < len(addresses); i++ { + tokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i]) + } +} + +// DisperseGRC20String receives a string of addresses and a string of tokens +// and parses them to be used in DisperseGRC20 +func DisperseGRC20String(addresses string, tokens string) { + parsedAddresses, err := parseAddresses(addresses) + if err != nil { + panic(err) + } + + parsedAmounts, parsedSymbols, err := parseTokens(tokens) + if err != nil { + panic(err) + } + + DisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols) +} + +// DisperseUgnotString receives a string of addresses and a string of amounts +// and parses them to be used in DisperseUgnot +func DisperseUgnotString(addresses string, amounts string) { + parsedAddresses, err := parseAddresses(addresses) + if err != nil { + panic(err) + } + + parsedAmounts, err := parseAmounts(amounts) + if err != nil { + panic(err) + } + + coins := make(std.Coins, len(parsedAmounts)) + for i, amount := range parsedAmounts { + coins[i] = std.NewCoin("ugnot", amount) + } + + DisperseUgnot(parsedAddresses, coins) +} diff --git a/examples/gno.land/r/demo/disperse/doc.gno b/examples/gno.land/r/demo/disperse/doc.gno new file mode 100644 index 00000000000..100aa92cb3d --- /dev/null +++ b/examples/gno.land/r/demo/disperse/doc.gno @@ -0,0 +1,19 @@ +// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses. +// +// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses +// on the Ethereum blockchain. +// +// Usage: +// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses. +// +// Example: +// Dispersing 200 coins to two addresses: +// - DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50") +// Dispersing 200 worth of a GRC20 token "TEST" to two addresses: +// - DisperseGRC20String("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150TEST,50TEST") +// +// Reference: +// - [the original dispere app](https://disperse.app/) +// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code) +// - [the gno disperse web app](https://gno-disperse.netlify.app/) +package disperse // import "gno.land/r/demo/disperse" diff --git a/examples/gno.land/r/demo/disperse/errors.gno b/examples/gno.land/r/demo/disperse/errors.gno new file mode 100644 index 00000000000..c054e658651 --- /dev/null +++ b/examples/gno.land/r/demo/disperse/errors.gno @@ -0,0 +1,12 @@ +package disperse + +import "errors" + +var ( + ErrNotEnoughCoin = errors.New("disperse: not enough coin sent in") + ErrNumAddrValMismatch = errors.New("disperse: number of addresses and values to send doesn't match") + ErrInvalidAddress = errors.New("disperse: invalid address") + ErrNegativeCoinAmount = errors.New("disperse: coin amount cannot be negative") + ErrMismatchBetweenSentAndParams = errors.New("disperse: mismatch between coins sent and params called") + ErrArgLenAndSentLenMismatch = errors.New("disperse: mismatch between coins sent and args called") +) diff --git a/examples/gno.land/r/demo/disperse/gno.mod b/examples/gno.land/r/demo/disperse/gno.mod new file mode 100644 index 00000000000..0ba9c88810a --- /dev/null +++ b/examples/gno.land/r/demo/disperse/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/disperse + +require gno.land/r/demo/grc20factory v0.0.0-latest diff --git a/examples/gno.land/r/demo/disperse/util.gno b/examples/gno.land/r/demo/disperse/util.gno new file mode 100644 index 00000000000..7101522572d --- /dev/null +++ b/examples/gno.land/r/demo/disperse/util.gno @@ -0,0 +1,67 @@ +package disperse + +import ( + "std" + "strconv" + "strings" + "unicode" +) + +func parseAddresses(addresses string) ([]std.Address, error) { + var ret []std.Address + + for _, str := range strings.Split(addresses, ",") { + addr := std.Address(str) + if !addr.IsValid() { + return nil, ErrInvalidAddress + } + + ret = append(ret, addr) + } + + return ret, nil +} + +func splitString(input string) (string, string) { + var pos int + for i, char := range input { + if !unicode.IsDigit(char) { + pos = i + break + } + } + return input[:pos], input[pos:] +} + +func parseTokens(tokens string) ([]uint64, []string, error) { + var amounts []uint64 + var symbols []string + + for _, token := range strings.Split(tokens, ",") { + amountStr, symbol := splitString(token) + amount, _ := strconv.Atoi(amountStr) + if amount < 0 { + return nil, nil, ErrNegativeCoinAmount + } + + amounts = append(amounts, uint64(amount)) + symbols = append(symbols, symbol) + } + + return amounts, symbols, nil +} + +func parseAmounts(amounts string) ([]int64, error) { + var ret []int64 + + for _, amt := range strings.Split(amounts, ",") { + amount, _ := strconv.Atoi(amt) + if amount < 0 { + return nil, ErrNegativeCoinAmount + } + + ret = append(ret, int64(amount)) + } + + return ret, nil +} diff --git a/examples/gno.land/r/demo/disperse/z_0_filetest.gno b/examples/gno.land/r/demo/disperse/z_0_filetest.gno new file mode 100644 index 00000000000..62a34cfdf26 --- /dev/null +++ b/examples/gno.land/r/demo/disperse/z_0_filetest.gno @@ -0,0 +1,32 @@ +// SEND: 200ugnot + +package main + +import ( + "std" + + "gno.land/r/demo/disperse" +) + +func main() { + disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") + mainaddr := std.DerivePkgAddr("main") + + std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOrigCaller(mainaddr) + + banker := std.GetBanker(std.BankerTypeRealmSend) + + mainbal := banker.GetCoins(mainaddr) + println("main before:", mainbal) + + banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 200}}) + disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50") + + mainbal = banker.GetCoins(mainaddr) + println("main after:", mainbal) +} + +// Output: +// main before: 200000200ugnot +// main after: 200000000ugnot diff --git a/examples/gno.land/r/demo/disperse/z_1_filetest.gno b/examples/gno.land/r/demo/disperse/z_1_filetest.gno new file mode 100644 index 00000000000..1e042d320f6 --- /dev/null +++ b/examples/gno.land/r/demo/disperse/z_1_filetest.gno @@ -0,0 +1,32 @@ +// SEND: 300ugnot + +package main + +import ( + "std" + + "gno.land/r/demo/disperse" +) + +func main() { + disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") + mainaddr := std.DerivePkgAddr("main") + + std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOrigCaller(mainaddr) + + banker := std.GetBanker(std.BankerTypeRealmSend) + + mainbal := banker.GetCoins(mainaddr) + println("main before:", mainbal) + + banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 300}}) + disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50") + + mainbal = banker.GetCoins(mainaddr) + println("main after:", mainbal) +} + +// Output: +// main before: 200000300ugnot +// main after: 200000100ugnot diff --git a/examples/gno.land/r/demo/disperse/z_2_filetest.gno b/examples/gno.land/r/demo/disperse/z_2_filetest.gno new file mode 100644 index 00000000000..163bb2fc1ab --- /dev/null +++ b/examples/gno.land/r/demo/disperse/z_2_filetest.gno @@ -0,0 +1,25 @@ +// SEND: 300ugnot + +package main + +import ( + "std" + + "gno.land/r/demo/disperse" +) + +func main() { + disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") + mainaddr := std.DerivePkgAddr("main") + + std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOrigCaller(mainaddr) + + banker := std.GetBanker(std.BankerTypeRealmSend) + + banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 100}}) + disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50") +} + +// Error: +// disperse: mismatch between coins sent and params called diff --git a/examples/gno.land/r/demo/disperse/z_3_filetest.gno b/examples/gno.land/r/demo/disperse/z_3_filetest.gno new file mode 100644 index 00000000000..eabed52fb38 --- /dev/null +++ b/examples/gno.land/r/demo/disperse/z_3_filetest.gno @@ -0,0 +1,45 @@ +// SEND: 300ugnot + +package main + +import ( + "std" + + "gno.land/r/demo/disperse" + tokens "gno.land/r/demo/grc20factory" +) + +func main() { + disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") + mainaddr := std.DerivePkgAddr("main") + beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") + beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") + + std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOrigCaller(mainaddr) + + banker := std.GetBanker(std.BankerTypeRealmSend) + + tokens.New("test", "TEST", 4, 0, 0) + tokens.Mint("TEST", mainaddr, 200) + + mainbal := tokens.BalanceOf("TEST", mainaddr) + println("main before:", mainbal) + + tokens.Approve("TEST", disperseAddr, 200) + + disperse.DisperseGRC20String("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150TEST,50TEST") + + mainbal = tokens.BalanceOf("TEST", mainaddr) + println("main after:", mainbal) + ben1bal := tokens.BalanceOf("TEST", beneficiary1) + println("beneficiary1:", ben1bal) + ben2bal := tokens.BalanceOf("TEST", beneficiary2) + println("beneficiary2:", ben2bal) +} + +// Output: +// main before: 200 +// main after: 0 +// beneficiary1: 150 +// beneficiary2: 50 diff --git a/examples/gno.land/r/demo/disperse/z_4_filetest.gno b/examples/gno.land/r/demo/disperse/z_4_filetest.gno new file mode 100644 index 00000000000..ebf4bed4473 --- /dev/null +++ b/examples/gno.land/r/demo/disperse/z_4_filetest.gno @@ -0,0 +1,48 @@ +// SEND: 300ugnot + +package main + +import ( + "std" + + "gno.land/r/demo/disperse" + tokens "gno.land/r/demo/grc20factory" +) + +func main() { + disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") + mainaddr := std.DerivePkgAddr("main") + beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") + beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") + + std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOrigCaller(mainaddr) + + banker := std.GetBanker(std.BankerTypeRealmSend) + + tokens.New("test1", "TEST1", 4, 0, 0) + tokens.Mint("TEST1", mainaddr, 200) + tokens.New("test2", "TEST2", 4, 0, 0) + tokens.Mint("TEST2", mainaddr, 200) + + mainbal := tokens.BalanceOf("TEST1", mainaddr) + tokens.BalanceOf("TEST2", mainaddr) + println("main before:", mainbal) + + tokens.Approve("TEST1", disperseAddr, 200) + tokens.Approve("TEST2", disperseAddr, 200) + + disperse.DisperseGRC20String("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "200TEST1,200TEST2") + + mainbal = tokens.BalanceOf("TEST1", mainaddr) + tokens.BalanceOf("TEST2", mainaddr) + println("main after:", mainbal) + ben1bal := tokens.BalanceOf("TEST1", beneficiary1) + tokens.BalanceOf("TEST2", beneficiary1) + println("beneficiary1:", ben1bal) + ben2bal := tokens.BalanceOf("TEST1", beneficiary2) + tokens.BalanceOf("TEST2", beneficiary2) + println("beneficiary2:", ben2bal) +} + +// Output: +// main before: 400 +// main after: 0 +// beneficiary1: 200 +// beneficiary2: 200 diff --git a/examples/gno.land/r/demo/echo/echo_test.gno b/examples/gno.land/r/demo/echo/echo_test.gno index 8e5be0704d5..92f4868e245 100644 --- a/examples/gno.land/r/demo/echo/echo_test.gno +++ b/examples/gno.land/r/demo/echo/echo_test.gno @@ -1,12 +1,12 @@ package echo -import "testing" +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/games/shifumi/gno.mod b/examples/gno.land/r/demo/games/shifumi/gno.mod new file mode 100644 index 00000000000..7a4fc173d3d --- /dev/null +++ b/examples/gno.land/r/demo/games/shifumi/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/games/shifumi + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/games/shifumi/shifumi.gno b/examples/gno.land/r/demo/games/shifumi/shifumi.gno new file mode 100644 index 00000000000..9094cb8fd69 --- /dev/null +++ b/examples/gno.land/r/demo/games/shifumi/shifumi.gno @@ -0,0 +1,120 @@ +package shifumi + +import ( + "errors" + "std" + "strconv" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" + + "gno.land/r/demo/users" +) + +const ( + empty = iota + rock + paper + scissors + last +) + +type game struct { + player1, player2 std.Address // shifumi is a 2 players game + move1, move2 int // can be empty, rock, paper, or scissors +} + +var games avl.Tree +var id seqid.ID + +func (g *game) play(player std.Address, move int) error { + if !(move > empty && move < last) { + return errors.New("invalid move") + } + if player != g.player1 && player != g.player2 { + return errors.New("invalid player") + } + if player == g.player1 && g.move1 == empty { + g.move1 = move + return nil + } + if player == g.player2 && g.move2 == empty { + g.move2 = move + return nil + } + return errors.New("already played") +} + +func (g *game) winner() int { + if g.move1 == empty || g.move2 == empty { + return -1 + } + if g.move1 == g.move2 { + return 0 + } + if g.move1 == rock && g.move2 == scissors || + g.move1 == paper && g.move2 == rock || + g.move1 == scissors && g.move2 == paper { + return 1 + } + return 2 +} + +// NewGame creates a new game where player1 is the caller and player2 the argument. +// A new game index is returned. +func NewGame(player std.Address) int { + games.Set(id.Next().String(), &game{player1: std.PrevRealm().Addr(), player2: player}) + return int(id) +} + +// Play executes a move for the game at index idx, where move can be: +// 1 (rock), 2 (paper), 3 (scissors). +func Play(idx, move int) { + v, ok := games.Get(seqid.ID(idx).String()) + if !ok { + panic("game not found") + } + if err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil { + panic(err) + } +} + +func Render(path string) string { + mov1 := []string{"", " 🤜 ", " 🫱 ", " 👉 "} + mov2 := []string{"", " 🤛 ", " 🫲 ", " 👈 "} + win := []string{"pending", "draw", "player1", "player2"} + + output := `# 👊 ✋ ✌️ Shifumi +Actions: +* [NewGame](shifumi?help&__func=NewGame) opponentAddress +* [Play](shifumi?help&__func=Play) gameIndex move (1=rock, 2=paper, 3=scissors) + + game | player1 | | player2 | | win + --- | --- | --- | --- | --- | --- +` + // Output the 100 most recent games. + maxGames := 100 + for n := int(id); n > 0 && int(id)-n < maxGames; n-- { + v, ok := games.Get(seqid.ID(n).String()) + if !ok { + continue + } + g := v.(*game) + output += strconv.Itoa(n) + " | " + + shortName(g.player1) + " | " + mov1[g.move1] + " | " + + shortName(g.player2) + " | " + mov2[g.move2] + " | " + + win[g.winner()+1] + "\n" + } + return output +} + +func shortName(addr std.Address) string { + user := users.GetUserByAddress(addr) + if user != nil { + return user.Name + } + if len(addr) < 10 { + return string(addr) + } + return string(addr)[:10] + "..." +} diff --git a/examples/gno.land/r/demo/grc20factory/gno.mod b/examples/gno.land/r/demo/grc20factory/gno.mod index db0000d998c..94e1928ca94 100644 --- a/examples/gno.land/r/demo/grc20factory/gno.mod +++ b/examples/gno.land/r/demo/grc20factory/gno.mod @@ -4,6 +4,7 @@ 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 gno.land/r/demo/grc20reg v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno index 8b4cf3c6c1a..5dfb6a760cc 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno @@ -3,6 +3,8 @@ package foo20 import ( "std" "testing" + + "gno.land/p/demo/uassert" ) func TestReadOnlyPublicMethods(t *testing.T) { @@ -29,9 +31,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { {"BalanceOf(unknown)", 0, func() uint64 { return BalanceOf("FOO", unknown) }}, } for _, tc := range tests { - if tc.fn() != tc.balance { - t.Errorf("%s: have: %d want: %d", tc.name, tc.fn(), tc.balance) - } + uassert.Equal(t, tc.balance, tc.fn(), "balance does not match") } } return @@ -50,9 +50,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { {"BalanceOf(unknown)", 10_000_000, func() uint64 { return BalanceOf("FOO", unknown) }}, } for _, tc := range tests { - if tc.fn() != tc.balance { - t.Errorf("%s: have: %d want: %d", tc.name, tc.fn(), tc.balance) - } + uassert.Equal(t, tc.balance, tc.fn(), "balance does not match") } } } 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 49e4167ae1a..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 @@ -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_1_a_filetest.gno b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno index 95d13c6f241..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 @@ -13,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_a_filetest.gno b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno index d9bee0e2edc..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 @@ -13,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/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 b2fc01c66b0..ffd8e60936f 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -6,6 +6,8 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" ) func TestRender(t *testing.T) { @@ -69,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 cff2ced8bc8..26349e481d4 100644 --- a/examples/gno.land/r/demo/microblog/gno.mod +++ b/examples/gno.land/r/demo/microblog/gno.mod @@ -4,5 +4,6 @@ require ( 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 5b8ec9da370..a3c8f04ee7f 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -6,6 +6,7 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" ) func TestMicroblog(t *testing.T) { @@ -16,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 @@ -63,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 @@ -85,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/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..1318e19eaf3 --- /dev/null +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -0,0 +1,144 @@ +package profile + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/mux" + "gno.land/p/demo/ufmt" +) + +var ( + fields = avl.NewTree() + router = mux.NewRouter() +) + +// Standard fields +const ( + DisplayName = "DisplayName" + Homepage = "Homepage" + Bio = "Bio" + Age = "Age" + Location = "Location" + Avatar = "Avatar" + GravatarEmail = "GravatarEmail" + AvailableForHiring = "AvailableForHiring" + InvalidField = "InvalidField" +) + +// Events +const ( + ProfileFieldCreated = "ProfileFieldCreated" + ProfileFieldUpdated = "ProfileFieldUpdated" +) + +// Field types used when emitting event +const FieldType = "FieldType" + +const ( + BoolField = "BoolField" + StringField = "StringField" + IntField = "IntField" +) + +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) bool { + addr := std.PrevRealm().Addr() + key := addr.String() + ":" + field + updated := fields.Set(key, value) + + event := ProfileFieldCreated + if updated { + event = ProfileFieldUpdated + } + + std.Emit(event, FieldType, StringField, field, value) + + return updated +} + +func SetIntField(field string, value int) bool { + addr := std.PrevRealm().Addr() + key := addr.String() + ":" + field + updated := fields.Set(key, value) + + event := ProfileFieldCreated + if updated { + event = ProfileFieldUpdated + } + + std.Emit(event, FieldType, IntField, field, string(value)) + + return updated +} + +func SetBoolField(field string, value bool) bool { + addr := std.PrevRealm().Addr() + key := addr.String() + ":" + field + updated := fields.Set(key, value) + + event := ProfileFieldCreated + if updated { + event = ProfileFieldUpdated + } + + std.Emit(event, FieldType, BoolField, field, ufmt.Sprintf("%t", value)) + + return updated +} + +// 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..3947897289e --- /dev/null +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -0,0 +1,142 @@ +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 new key + updated := SetStringField(DisplayName, "Alice foo") + uassert.Equal(t, updated, false) + updated = SetStringField(Homepage, "https://example.com") + uassert.Equal(t, updated, false) + + // Update the key + updated = SetStringField(DisplayName, "Alice foo") + uassert.Equal(t, updated, true) + + // 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 new key + updated := SetIntField(Age, 30) + uassert.Equal(t, updated, false) + + // Update the key + updated = SetIntField(Age, 30) + uassert.Equal(t, updated, true) + + // 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 + updated := SetBoolField(AvailableForHiring, true) + uassert.Equal(t, updated, false) + + // Update the key + updated = SetBoolField(AvailableForHiring, true) + uassert.Equal(t, updated, true) + + // Get after setting + hiring = GetBoolField(charlie, AvailableForHiring, false) + uassert.Equal(t, true, hiring) +} + +func TestMultipleProfiles(t *testing.T) { + // Set profile for user1 + std.TestSetRealm(std.NewUserRealm(user1)) + updated := SetStringField(DisplayName, "User One") + uassert.Equal(t, updated, false) + + // Set profile for user2 + std.TestSetRealm(std.NewUserRealm(user2)) + updated = SetStringField(DisplayName, "User Two") + uassert.Equal(t, updated, false) + + // 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) +} + +func TestArbitraryStringField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set arbitrary string field + updated := SetStringField("MyEmail", "my@email.com") + uassert.Equal(t, updated, false) + + val := GetStringField(user1, "MyEmail", "") + uassert.Equal(t, val, "my@email.com") +} + +func TestArbitraryIntField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set arbitrary int field + updated := SetIntField("MyIncome", 100_000) + uassert.Equal(t, updated, false) + + val := GetIntField(user1, "MyIncome", 0) + uassert.Equal(t, val, 100_000) +} + +func TestArbitraryBoolField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set arbitrary int field + updated := SetBoolField("IsWinner", true) + uassert.Equal(t, updated, false) + + val := GetBoolField(user1, "IsWinner", false) + uassert.Equal(t, val, true) +} 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..79d1078a997 --- /dev/null +++ b/examples/gno.land/r/demo/profile/render.gno @@ -0,0 +1,103 @@ +package profile + +import ( + "bytes" + "net/url" + "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, url.QueryEscape(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/tests/gno.mod b/examples/gno.land/r/demo/tests/gno.mod index f34d41d327a..c51571e7d04 100644 --- a/examples/gno.land/r/demo/tests/gno.mod +++ b/examples/gno.land/r/demo/tests/gno.mod @@ -1,3 +1,6 @@ module gno.land/r/demo/tests -require gno.land/r/demo/tests/subtests v0.0.0-latest +require ( + 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/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index 2489a2214ba..e7fde94ea08 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" ) @@ -101,3 +102,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/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/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod index a2ee2ea86ba..61b11c09b80 100644 --- a/examples/gno.land/r/demo/users/gno.mod +++ b/examples/gno.land/r/demo/users/gno.mod @@ -2,5 +2,6 @@ module gno.land/r/demo/users require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/avlhelpers v0.0.0-latest gno.land/p/demo/users v0.0.0-latest ) 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 5b4b8ec2c14..4a0b9c1caf7 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -7,6 +7,7 @@ import ( "strings" "gno.land/p/demo/avl" + "gno.land/p/demo/avlhelpers" "gno.land/p/demo/users" ) @@ -14,13 +15,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 +37,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.NewCoin("ugnot", minFee) + if inviter == "" { // banker := std.GetBanker(std.BankerTypeOrigSend) if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { @@ -55,19 +60,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 { @@ -235,15 +256,42 @@ func GetUserByAddressOrName(input users.AddressOrName) *users.User { return GetUserByAddress(std.Address(input)) } +// Get a list of user names starting from the given prefix. Limit the +// number of results to maxResults. (This can be used for a name search tool.) +func ListUsersByPrefix(prefix string, maxResults int) []string { + return avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults) +} + func Resolve(input users.AddressOrName) std.Address { name, isName := input.GetName() if !isName { return std.Address(input) // TODO check validity } + user := GetUserByName(name) return user.Address } +// Add restricted name to the list +func AdminAddRestrictedName(name string) { + // assert CallTx call. + std.AssertOriginCall() + // get caller + caller := std.GetOrigCaller() + // assert admin + if caller != admin { + panic("unauthorized") + } + + if user := GetUserByName(name); user != nil { + panic("already registered name") + } + + // register restricted name + + restricted.Set(name, true) +} + //---------------------------------------- // Constants 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 9095057076c..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,6 +1,6 @@ package main -// SEND: 199000000ugnot +// SEND: 19900000ugnot import ( "gno.land/r/demo/users" @@ -12,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_10_filetest.gno b/examples/gno.land/r/demo/users/z_10_filetest.gno index 878fa296fdd..078058c0703 100644 --- a/examples/gno.land/r/demo/users/z_10_filetest.gno +++ b/examples/gno.land/r/demo/users/z_10_filetest.gno @@ -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_12_filetest.gno b/examples/gno.land/r/demo/users/z_12_filetest.gno new file mode 100644 index 00000000000..0fb7d27bd34 --- /dev/null +++ b/examples/gno.land/r/demo/users/z_12_filetest.gno @@ -0,0 +1,49 @@ +package main + +// SEND: 200000000ugnot + +import ( + "strconv" + + "gno.land/r/demo/users" +) + +func main() { + users.Register("", "alicia", "my profile") + + { + // Normal usage + names := users.ListUsersByPrefix("a", 1) + println("# names: " + strconv.Itoa(len(names))) + println("name: " + names[0]) + } + + { + // Empty prefix: match all + names := users.ListUsersByPrefix("", 1) + println("# names: " + strconv.Itoa(len(names))) + println("name: " + names[0]) + } + + { + // The prefix is before "alicia" + names := users.ListUsersByPrefix("alich", 1) + println("# names: " + strconv.Itoa(len(names))) + } + + { + // The prefix is after the last name + names := users.ListUsersByPrefix("y", 10) + println("# names: " + strconv.Itoa(len(names))) + } + + // More tests are in p/demo/avlhelpers +} + +// Output: +// # names: 1 +// name: alicia +// # names: 1 +// name: alicia +// # names: 0 +// # names: 0 diff --git a/examples/gno.land/r/demo/users/z_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 008ba03a936..85305fff1ad 100644 --- a/examples/gno.land/r/demo/users/z_6_filetest.gno +++ b/examples/gno.land/r/demo/users/z_6_filetest.gno @@ -6,7 +6,7 @@ import ( "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/gnoland/events/administration.gno b/examples/gno.land/r/gnoland/events/administration.gno new file mode 100644 index 00000000000..02914adee69 --- /dev/null +++ b/examples/gno.land/r/gnoland/events/administration.gno @@ -0,0 +1,26 @@ +package events + +import ( + "std" + + "gno.land/p/demo/ownable/exts/authorizable" +) + +var ( + su = std.Address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5") // @leohhhn + auth = authorizable.NewAuthorizableWithAddress(su) +) + +// GetOwner gets the owner of the events realm +func GetOwner() std.Address { + return auth.Owner() +} + +// AddModerator adds a moderator to the events realm +func AddModerator(mod std.Address) { + auth.AssertCallerIsOwner() + + if err := auth.AddToAuthList(mod); err != nil { + panic(err) + } +} diff --git a/examples/gno.land/r/gnoland/events/errors.gno b/examples/gno.land/r/gnoland/events/errors.gno new file mode 100644 index 00000000000..fb44d3c9f82 --- /dev/null +++ b/examples/gno.land/r/gnoland/events/errors.gno @@ -0,0 +1,18 @@ +package events + +import ( + "errors" + "strconv" +) + +var ( + ErrEmptyName = errors.New("event name cannot be empty") + ErrNoSuchID = errors.New("event with specified ID does not exist") + ErrMinWidgetSize = errors.New("you need to request at least 1 event to render") + ErrMaxWidgetSize = errors.New("maximum number of events in widget is" + strconv.Itoa(MaxWidgetSize)) + ErrDescriptionTooLong = errors.New("event description is too long") + ErrInvalidStartTime = errors.New("invalid start time format") + ErrInvalidEndTime = errors.New("invalid end time format") + ErrEndBeforeStart = errors.New("end time cannot be before start time") + ErrStartEndTimezonemMismatch = errors.New("start and end timezones are not the same") +) diff --git a/examples/gno.land/r/gnoland/events/events.gno b/examples/gno.land/r/gnoland/events/events.gno index 9c2708a112e..0984edf75a9 100644 --- a/examples/gno.land/r/gnoland/events/events.gno +++ b/examples/gno.land/r/gnoland/events/events.gno @@ -1,240 +1,199 @@ +// Package events allows you to upload data about specific IRL/online events +// It includes dynamic support for updating rendering events based on their +// status, ie if they are upcoming, in progress, or in the past. package events 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 - -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"} + "sort" + "std" + "strings" + "time" - // 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() -} + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" +) -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."), +type ( + Event struct { + id string + name string // name of event + description string // short description of event + link string // link to auth corresponding web2 page, ie eventbrite/luma or conference page + location string // location of the event + startTime time.Time // given in RFC3339 + endTime time.Time // end time of the event, given in RFC3339 } -} - -func upcomingEvents() ui.Element { - return ui.Element{ - ui.H2("Upcoming Events"), - ui.Text(`
-
- -### 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 + eventsSlice []*Event +) -[Learn More](https://nebular.builders/) -
+var ( + events = make(eventsSlice, 0) // sorted + idCounter seqid.ID +) -
+const ( + maxDescLength = 100 + EventAdded = "EventAdded" + EventDeleted = "EventDeleted" + EventEdited = "EventEdited" +) -
-
+// AddEvent adds auth new event +// Start time & end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00 +func AddEvent(name, description, link, location, startTime, endTime string) (string, error) { + auth.AssertOnAuthList() -
-
`), + if strings.TrimSpace(name) == "" { + return "", ErrEmptyName } -} - -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) - -
- -
- -### 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/) + if len(description) > maxDescLength { + return "", ufmt.Errorf("%s: provided length is %d, maximum is %d", ErrDescriptionTooLong, len(description), maxDescLength) + } -
-
+ // Parse times + st, et, err := parseTimes(startTime, endTime) + if err != nil { + return "", err + } -### BUIDL Asia + id := idCounter.Next().String() + e := &Event{ + id: id, + name: name, + description: description, + link: link, + location: location, + startTime: st, + endTime: et, + } -- **Proof of Contribution in Gno.land** -- Seoul, June 6, 2023 -- Manfred Touron + events = append(events, e) + sort.Sort(events) -[Learn more](https://www.buidl.asia/) + std.Emit(EventAdded, + "id", + e.id, + ) -
-
+ return id, nil +} -### Game Developer Conference +// DeleteEvent deletes an event with auth given ID +func DeleteEvent(id string) { + auth.AssertOnAuthList() -- **Side Event: Web3 Gaming Apps Powered by Gno** -- San Francisco, Mach 23, 2023 -- Jae Kwon + e, idx, err := GetEventByID(id) + if err != nil { + panic(err) + } -[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + events = append(events[:idx], events[idx+1:]...) -
-
+ std.Emit(EventDeleted, + "id", + e.id, + ) +} -### EthDenver +// EditEvent edits an event with auth given ID +// It only updates values corresponding to non-empty arguments sent with the call +// Note: if you need to update the start time or end time, you need to provide both every time +func EditEvent(id string, name, description, link, location, startTime, endTime string) { + auth.AssertOnAuthList() -- **Side Event: Discover Gno.land** -- Denver, Feb 24 - Mar 5, 2023 -- Jae Kwon + e, _, err := GetEventByID(id) + if err != nil { + panic(err) + } -[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + // Set only valid values + if strings.TrimSpace(name) != "" { + e.name = name + } -
-
+ if strings.TrimSpace(description) != "" { + e.description = description + } -### Istanbul Blockchain Week + if strings.TrimSpace(link) != "" { + e.link = link + } -- Istanbul, Nov 14 - 17, 2022 -- Manfred Touron + if strings.TrimSpace(location) != "" { + e.location = location + } -[Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4) + if strings.TrimSpace(startTime) != "" || strings.TrimSpace(endTime) != "" { + st, et, err := parseTimes(startTime, endTime) + if err != nil { + panic(err) // need to also revert other state changes + } -
-
+ oldStartTime := e.startTime + e.startTime = st + e.endTime = et -### Web Summit Buckle Up and Build with Cosmos + // If sort order was disrupted, sort again + if oldStartTime != e.startTime { + sort.Sort(events) + } + } -- Lisbon, Nov 1 - 4, 2022 -- Manfred Touron + std.Emit(EventEdited, + "id", + e.id, + ) +} -
-
+func GetEventByID(id string) (*Event, int, error) { + for i, event := range events { + if event.id == id { + return event, i, nil + } + } -### Cosmoverse + return nil, -1, ErrNoSuchID +} -- Medallin, Sept 26 - 28, 2022 -- Manfred Touron +// Len returns the length of the slice +func (m eventsSlice) Len() int { + return len(m) +} -[Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk) +// Less compares the startTime fields of two elements +// In this case, events will be sorted by largest startTime first (upcoming > past) +func (m eventsSlice) Less(i, j int) bool { + return m[i].startTime.After(m[j].startTime) +} -
-
+// Swap swaps two elements in the slice +func (m eventsSlice) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} -### Berlin Blockchain Week Buckle Up and Build with Cosmos +// parseTimes parses the start and end time for an event and checks for possible errors +func parseTimes(startTime, endTime string) (time.Time, time.Time, error) { + st, err := time.Parse(time.RFC3339, startTime) + if err != nil { + return time.Time{}, time.Time{}, ufmt.Errorf("%s: %s", ErrInvalidStartTime, err.Error()) + } -- Berlin, Sept 11 - 18, 2022 + et, err := time.Parse(time.RFC3339, endTime) + if err != nil { + return time.Time{}, time.Time{}, ufmt.Errorf("%s: %s", ErrInvalidEndTime, err.Error()) + } -[Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI) + if et.Before(st) { + return time.Time{}, time.Time{}, ErrEndBeforeStart + } -
-
`), + _, stOffset := st.Zone() + _, etOffset := et.Zone() + if stOffset != etOffset { + return time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch } + + return st, et, nil } diff --git a/examples/gno.land/r/gnoland/events/events_filetest.gno b/examples/gno.land/r/gnoland/events/events_filetest.gno deleted file mode 100644 index 46ee273414d..00000000000 --- a/examples/gno.land/r/gnoland/events/events_filetest.gno +++ /dev/null @@ -1,226 +0,0 @@ -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/events_test.gno b/examples/gno.land/r/gnoland/events/events_test.gno new file mode 100644 index 00000000000..357857352d8 --- /dev/null +++ b/examples/gno.land/r/gnoland/events/events_test.gno @@ -0,0 +1,200 @@ +package events + +import ( + "std" + "strings" + "testing" + "time" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var ( + suRealm = std.NewUserRealm(su) + + now = "2009-02-13T23:31:30Z" // time.Now() is hardcoded to this value in the gno test machine currently + parsedTimeNow, _ = time.Parse(time.RFC3339, now) +) + +func TestAddEvent(t *testing.T) { + std.TestSetOrigCaller(su) + std.TestSetRealm(suRealm) + + e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) + e1End := e1Start.Add(time.Hour * 4) + + AddEvent("Event 1", "this event is upcoming", "gno.land", "gnome land", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339)) + + got := renderHome(false) + + if !strings.Contains(got, "Event 1") { + t.Fatalf("Expected to find Event 1 in render") + } + + e2Start := parsedTimeNow.Add(-time.Hour * 24 * 5) + e2End := e2Start.Add(time.Hour * 4) + + AddEvent("Event 2", "this event is in the past", "gno.land", "gnome land", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339)) + + got = renderHome(false) + + upcomingPos := strings.Index(got, "## Upcoming events") + pastPos := strings.Index(got, "## Past events") + + e1Pos := strings.Index(got, "Event 1") + e2Pos := strings.Index(got, "Event 2") + + // expected index ordering: upcoming < e1 < past < e2 + if e1Pos < upcomingPos || e1Pos > pastPos { + t.Fatalf("Expected to find Event 1 in Upcoming events") + } + + if e2Pos < upcomingPos || e2Pos < pastPos || e2Pos < e1Pos { + t.Fatalf("Expected to find Event 2 on auth different pos") + } + + // larger index => smaller startTime (future => past) + if events[0].startTime.Unix() < events[1].startTime.Unix() { + t.Fatalf("expected ordering to be different") + } +} + +func TestAddEventErrors(t *testing.T) { + std.TestSetOrigCaller(su) + std.TestSetRealm(suRealm) + + _, err := AddEvent("", "sample desc", "gno.land", "gnome land", "2009-02-13T23:31:31Z", "2009-02-13T23:33:31Z") + uassert.ErrorIs(t, err, ErrEmptyName) + + _, err = AddEvent("sample name", "sample desc", "gno.land", "gnome land", "", "2009-02-13T23:33:31Z") + uassert.ErrorContains(t, err, ErrInvalidStartTime.Error()) + + _, err = AddEvent("sample name", "sample desc", "gno.land", "gnome land", "2009-02-13T23:31:31Z", "") + uassert.ErrorContains(t, err, ErrInvalidEndTime.Error()) + + _, err = AddEvent("sample name", "sample desc", "gno.land", "gnome land", "2009-02-13T23:31:31Z", "2009-02-13T23:30:31Z") + uassert.ErrorIs(t, err, ErrEndBeforeStart) + + _, err = AddEvent("sample name", "sample desc", "gno.land", "gnome land", "2009-02-13T23:31:31+06:00", "2009-02-13T23:33:31+02:00") + uassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch) + + tooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma` + _, err = AddEvent("sample name", tooLongDesc, "gno.land", "gnome land", "2009-02-13T23:31:31Z", "2009-02-13T23:33:31Z") + uassert.ErrorContains(t, err, ErrDescriptionTooLong.Error()) +} + +func TestDeleteEvent(t *testing.T) { + events = nil // remove elements from previous tests - see issue #1982 + + e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) + e1End := e1Start.Add(time.Hour * 4) + + id, _ := AddEvent("ToDelete", "description", "gno.land", "gnome land", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339)) + + got := renderHome(false) + + if !strings.Contains(got, "ToDelete") { + t.Fatalf("Expected to find ToDelete event in render") + } + + DeleteEvent(id) + got = renderHome(false) + + if strings.Contains(got, "ToDelete") { + t.Fatalf("Did not expect to find ToDelete event in render") + } +} + +func TestEditEvent(t *testing.T) { + events = nil // remove elements from previous tests - see issue #1982 + + e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) + e1End := e1Start.Add(time.Hour * 4) + loc := "gnome land" + + id, _ := AddEvent("ToDelete", "description", "gno.land", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339)) + + newName := "New Name" + newDesc := "Normal description" + newLink := "new Link" + newST := e1Start.Add(time.Hour) + newET := newST.Add(time.Hour) + + EditEvent(id, newName, newDesc, newLink, "", newST.Format(time.RFC3339), newET.Format(time.RFC3339)) + edited, _, _ := GetEventByID(id) + + // Check updated values + uassert.Equal(t, edited.name, newName) + uassert.Equal(t, edited.description, newDesc) + uassert.Equal(t, edited.link, newLink) + uassert.True(t, edited.startTime.Equal(newST)) + uassert.True(t, edited.endTime.Equal(newET)) + + // Check if the old values are the same + uassert.Equal(t, edited.location, loc) +} + +func TestInvalidEdit(t *testing.T) { + events = nil // remove elements from previous tests - see issue #1982 + + uassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() { + EditEvent("123123", "", "", "", "", "", "") + }) +} + +func TestParseTimes(t *testing.T) { + // times not provided + // end time before start time + // timezone Missmatch + + _, _, err := parseTimes("", "") + uassert.ErrorContains(t, err, ErrInvalidStartTime.Error()) + + _, _, err = parseTimes(now, "") + uassert.ErrorContains(t, err, ErrInvalidEndTime.Error()) + + _, _, err = parseTimes("2009-02-13T23:30:30Z", "2009-02-13T21:30:30Z") + uassert.ErrorContains(t, err, ErrEndBeforeStart.Error()) + + _, _, err = parseTimes("2009-02-10T23:30:30+02:00", "2009-02-13T21:30:33+05:00") + uassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error()) +} + +func TestRenderEventWidget(t *testing.T) { + events = nil // remove elements from previous tests - see issue #1982 + + // No events yet + out, err := RenderEventWidget(1) + uassert.NoError(t, err) + uassert.Equal(t, out, "No events.") + + // Too many events + out, err = RenderEventWidget(MaxWidgetSize + 1) + uassert.ErrorIs(t, err, ErrMaxWidgetSize) + + // Too little events + out, err = RenderEventWidget(0) + uassert.ErrorIs(t, err, ErrMinWidgetSize) + + // Ordering & if requested amt is larger than the num of events that exist + e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) + e1End := e1Start.Add(time.Hour * 4) + + e2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1 + e2End := e2Start.Add(time.Hour * 4) + + _, err = AddEvent("Event 1", "description", "gno.land", "loc", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339)) + urequire.NoError(t, err) + + _, err = AddEvent("Event 2", "description", "gno.land", "loc", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339)) + urequire.NoError(t, err) + + out, err = RenderEventWidget(MaxWidgetSize) + urequire.NoError(t, err) + + uniqueSequence := "- [" // sequence that is displayed once per each event as per the RenderEventWidget function + uassert.Equal(t, 2, strings.Count(out, uniqueSequence)) + + uassert.True(t, strings.Index(out, "Event 1") > strings.Index(out, "Event 2")) +} diff --git a/examples/gno.land/r/gnoland/events/gno.mod b/examples/gno.land/r/gnoland/events/gno.mod index ec781c7cf10..bd3e4652b04 100644 --- a/examples/gno.land/r/gnoland/events/gno.mod +++ b/examples/gno.land/r/gnoland/events/gno.mod @@ -1,3 +1,9 @@ module gno.land/r/gnoland/events -require gno.land/p/demo/ui v0.0.0-latest +require ( + gno.land/p/demo/ownable/exts/authorizable v0.0.0-latest + gno.land/p/demo/seqid 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/r/gnoland/events/rendering.gno b/examples/gno.land/r/gnoland/events/rendering.gno new file mode 100644 index 00000000000..d98879c68f6 --- /dev/null +++ b/examples/gno.land/r/gnoland/events/rendering.gno @@ -0,0 +1,145 @@ +package events + +import ( + "bytes" + "time" + + "gno.land/p/demo/ufmt" +) + +const ( + MaxWidgetSize = 5 +) + +// RenderEventWidget shows up to eventsToRender of the latest events to a caller +func RenderEventWidget(eventsToRender int) (string, error) { + numOfEvents := len(events) + if numOfEvents == 0 { + return "No events.", nil + } + + if eventsToRender > MaxWidgetSize { + return "", ErrMaxWidgetSize + } + + if eventsToRender < 1 { + return "", ErrMinWidgetSize + } + + if eventsToRender > numOfEvents { + eventsToRender = numOfEvents + } + + output := "" + + for _, event := range events[:eventsToRender] { + output += ufmt.Sprintf("- [%s](%s)\n", event.name, event.link) + } + + return output, nil +} + +// renderHome renders the home page of the events realm +func renderHome(admin bool) string { + output := "# gno.land events\n\n" + + if len(events) == 0 { + output += "No upcoming or past events." + return output + } + + output += "Below is a list of all gno.land events, including in progress, upcoming, and past ones.\n\n" + output += "---\n\n" + + var ( + inProgress = "" + upcoming = "" + past = "" + now = time.Now() + ) + + for _, e := range events { + if now.Before(e.startTime) { + upcoming += e.Render(admin) + } else if now.After(e.endTime) { + past += e.Render(admin) + } else { + inProgress += e.Render(admin) + } + } + + if upcoming != "" { + // Add upcoming events + output += "## Upcoming events\n\n" + output += "
" + + output += upcoming + + output += "
\n\n" + output += "---\n\n" + } + + if inProgress != "" { + output += "## Currently in progress\n\n" + output += "
" + + output += inProgress + + output += "
\n\n" + output += "---\n\n" + } + + if past != "" { + // Add past events + output += "## Past events\n\n" + output += "
" + + output += past + + output += "
\n\n" + } + + return output +} + +// Render returns the markdown representation of a single event instance +func (e Event) Render(admin bool) string { + var buf bytes.Buffer + + buf.WriteString("
\n\n") + buf.WriteString(ufmt.Sprintf("### %s\n\n", e.name)) + buf.WriteString(ufmt.Sprintf("%s\n\n", e.description)) + buf.WriteString(ufmt.Sprintf("**Location:** %s\n\n", e.location)) + + _, offset := e.startTime.Zone() // offset is in seconds + hoursOffset := offset / (60 * 60) + sign := "" + if offset >= 0 { + sign = "+" + } + + buf.WriteString(ufmt.Sprintf("**Starts:** %s UTC%s%d\n\n", e.startTime.Format("02 Jan 2006, 03:04 PM"), sign, hoursOffset)) + buf.WriteString(ufmt.Sprintf("**Ends:** %s UTC%s%d\n\n", e.endTime.Format("02 Jan 2006, 03:04 PM"), sign, hoursOffset)) + + if admin { + buf.WriteString(ufmt.Sprintf("[EDIT](/r/gnoland/events?help&__func=EditEvent&id=%s)\n\n", e.id)) + buf.WriteString(ufmt.Sprintf("[DELETE](/r/gnoland/events?help&__func=DeleteEvent&id=%s)\n\n", e.id)) + } + + if e.link != "" { + buf.WriteString(ufmt.Sprintf("[See more](%s)\n\n", e.link)) + } + + buf.WriteString("
") + + return buf.String() +} + +// Render is the main rendering entry point +func Render(path string) string { + if path == "admin" { + return renderHome(true) + } + + return renderHome(false) +} diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod index 2864958930c..c208ad421c9 100644 --- a/examples/gno.land/r/gnoland/home/gno.mod +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -1,7 +1,9 @@ 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 + gno.land/r/gnoland/events 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 072a39bd012..921492d81b4 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -3,16 +3,27 @@ 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" + events "gno.land/r/gnoland/events" ) // XXX: p/demo/ui API is crappy, we need to make it more idiomatic // XXX: use an updatable block system to update content from a DAO // XXX: var blocks avl.Tree +var ( + override string + admin = ownable.NewWithAddress("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.Classes = []string{"gno-tmpl-section"} @@ -25,7 +36,7 @@ func Render(_ string) string { dom.Body.Append( ui.Columns{3, []ui.Element{ lastBlogposts(4), - upcomingEvents(4), + upcomingEvents(), lastContributions(4), }}, ) @@ -58,7 +69,7 @@ func Render(_ string) string { func lastBlogposts(limit int) ui.Element { posts := blog.RenderLastPostsWidget(limit) return ui.Element{ - ui.H3("Latest Blogposts"), + ui.H3("[Latest Blogposts](/r/gnoland/blog)"), ui.Text(posts), } } @@ -71,17 +82,17 @@ func lastContributions(limit int) ui.Element { } } -func upcomingEvents(limit int) ui.Element { +func upcomingEvents() ui.Element { + out, _ := events.RenderEventWidget(events.MaxWidgetSize) return ui.Element{ - ui.H3("Upcoming Events"), - // TODO: replace with r/gnoland/events - ui.Text("[View upcoming events](/events)"), + ui.H3("[Latest Events](/r/gnoland/events)"), + ui.Text(out), } } 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.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."), } @@ -235,6 +246,7 @@ func discoverLinks() ui.Element { - Tokenomics (soon) - [Partners, Fund, Grants](/partners) - [Explore the Ecosystem](/ecosystem) +- [Careers](https://jobs.lever.co/allinbits?department=Gno.land) @@ -257,7 +269,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) -- Testnet 4 (upcoming) +- [Testnet 4](https://test4.gno.land/) (Launched July 2024!) - [Testnet 3](https://test3.gno.land/) (archive) - [Testnet 2](https://test2.gno.land/) (archive) - Testnet Faucet Hub (soon) @@ -266,3 +278,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 2028a02c913..b70b22c80af 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -11,7 +11,7 @@ func main() { // // # 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. +// ### 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. @@ -33,6 +33,7 @@ func main() { // - Tokenomics (soon) // - [Partners, Fund, Grants](/partners) // - [Explore the Ecosystem](/ecosystem) +// - [Careers](https://jobs.lever.co/allinbits?department=Gno.land) // // // @@ -55,7 +56,7 @@ func main() { // - [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 4](https://test4.gno.land/) (Launched July 2024!) // - [Testnet 3](https://test3.gno.land/) (archive) // - [Testnet 2](https://test2.gno.land/) (archive) // - Testnet Faucet Hub (soon) @@ -67,15 +68,15 @@ func main() { //
//
// -// ### Latest Blogposts +// ### [Latest Blogposts](/r/gnoland/blog) // // No posts. //
//
// -// ### Upcoming Events +// ### [Latest Events](/r/gnoland/events) // -// [View upcoming events](/events) +// No events. //
//
// 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..4f21b90a3c2 --- /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: ownable: caller is not owner diff --git a/examples/gno.land/r/gnoland/pages/page_contribute.gno b/examples/gno.land/r/gnoland/pages/page_contribute.gno new file mode 100644 index 00000000000..3cdef10d9dc --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_contribute.gno @@ -0,0 +1,106 @@ +package gnopages + +func init() { + path := "contribute" + title := "Contributor Ecosystem: Call for Contributions" + body := ` + +gno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives. + +gno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming. + +As an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions. + +## Where to get started + +If you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens. + +A good place where to start are the issues tagged ["good first issue"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works. + +## Gno Bounties + +Additionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms. + +The Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the ["bounty" label](https://github.com/gnolang/gno/labels/bounty). + +Recommendations on participating in the gno.land Bounty Program: + +- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment. +- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible. + - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty. + - Make sure to reference the bounty issue on the PR description you're writing. + - After submitting the 'draft' PR, continue working until you are ready to mark the PR as "ready for review". + - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward. +- Ask for clarification early if an element on the requirements or implementation design is unclear. + - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track. + - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code. + - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope. + +You may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on. + +Don't fear your work being "stolen": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen: + +- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both). +- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part. + - If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution. +- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an "outstanding contribution"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools. + +Participants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/1aXrZ6japdAykB5FLmHCCeBZTo-2tbZQHSQi79ITaTK0). + +### Bounty sizes + +Each bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time. + +In some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient. + +The value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback. + + +t-shirt size | expected compensation +-------------|----------------------- +[XS] | $ 500 +[S] | $ 1000 +[M] | $ 2000 +[L] | $ 4000 +[XL] | $ 8000 +_[XXL]_ \* | $ 16000 +_[3XL]_ \* | $ 32000 + +[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS +[S]: https://github.com/gnolang/gno/labels/bounty%2FS +[M]: https://github.com/gnolang/gno/labels/bounty%2FM +[L]: https://github.com/gnolang/gno/labels/bounty%2FL +[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL +[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL +[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL + +\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties. + +## gno.land Grants + +The gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land. + + + +## Join Game of Realms + +Game of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors). + +These contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team. + +The selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub. + +You can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future. + +There are a variety of ways to make your contributions count: + +- Core code contributions +- Realm and pure package development +- Validator tooling +- Developer tooling +- Tutorials and documentation + +To start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.` + + _ = b.NewPost("", path, title, body, "2024-09-05T00:00:00Z", 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 e1a540c98a5..c6e7c22ae48 100644 --- a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno +++ b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno @@ -14,6 +14,14 @@ functions in your code using the repo. Visit the playground at [play.gno.land](https://play.gno.land)! +### [Gno Studio Connect](https://gno.studio/connect) + +Gno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage +with gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact +with any realm’s exposed function(s) on gno.land. + +See your realm interactions in [Gno Studio Connect](https://gno.studio/connect) + ### [Gnoscan](https://gnoscan.io) Developed by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find @@ -26,7 +34,7 @@ Explore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)! 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. +high-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/) ### Gnoswap @@ -38,7 +46,13 @@ automated market maker (AMM) protocol written in Gno that allows for permissionl Flippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles on to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player must memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later -be assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. +be assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip) + +### Gno Native Kit + +[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language. + + ` ) _ = b.NewPost("", path, title, body, "2022-05-20T13:17:23Z", nil, nil) diff --git a/examples/gno.land/r/gnoland/pages/page_gor.gno b/examples/gno.land/r/gnoland/pages/page_gor.gno deleted file mode 100644 index d46e9cb0ccc..00000000000 --- a/examples/gno.land/r/gnoland/pages/page_gor.gno +++ /dev/null @@ -1,221 +0,0 @@ -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." - 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 competition is currently in phase one – for advanced developers only. - -Once the necessary tools to start phase two are ready, we’ll open up the competition to newer devs and non-technical contributors. - -If you want to stack ATOM rewards and play a key role in the success of gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today. - -
- -
-
-
- -## Phase I. (ongoing) - -- - -- - -- - -
-
- -## Phase II. (Locked) - -
-
-
- -
- -
- -## Evaluation DAO - -This complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the gno.land platform are fairly rewarded. - -
- - - - - - - -
- -Game of Realms participants and core contributors are still in discussions, proposing additional ideas, and seeing how the proposal for the Evaluation DAO evolves over time. - -
- - - -
- -See [GitHub issue 519](https://github.com/gnolang/gno/issues/519) for the most up-to-date discussion so far on how voting should work for the DAO, what the responsibilities are, how to join, etc. - -
- - - - - - - - - - - - - - - - - -
-
- -
- -## Tutorials - -To progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to gno.land. - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -## Governance Module - -Can you define and implement a governance contract suite that rivals existing ones, such as the Cosmos Hub? Show us how! We’re looking for the fairest and most efficient governance solution possible. - -
- - - - - - - -
- -Game of Realms participants and core contributors have made significant progress teaming up to complete this challenge but discussions and additional ideas are still ongoing. - -
- - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -## Register Now - - -
-
- -
-
- - -
- -
- - -
- - -
-
-
- -` - _ = b.NewPost("", path, title, body, "2022-05-20T13:17:26Z", nil, nil) -} diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno index c7972686bb3..074e80e1892 100644 --- a/examples/gno.land/r/gnoland/pages/pages_test.gno +++ b/examples/gno.land/r/gnoland/pages/pages_test.gno @@ -11,7 +11,7 @@ func TestHome(t *testing.T) { expectedSubtrings := []string{ "/r/gnoland/pages:p/tokenomics", "/r/gnoland/pages:p/start", - "/r/gnoland/pages:p/gor", + "/r/gnoland/pages:p/contribute", "/r/gnoland/pages:p/about", "/r/gnoland/pages:p/gnolang", } diff --git a/examples/gno.land/r/gov/dao/dao.gno b/examples/gno.land/r/gov/dao/dao.gno index 6fcb8b39949..632935dafed 100644 --- a/examples/gno.land/r/gov/dao/dao.gno +++ b/examples/gno.land/r/gov/dao/dao.gno @@ -61,7 +61,7 @@ func Propose(comment string, executor pproposal.Executor) int { if executor == nil { panic(msgMissingExecutor) } - caller := std.PrevRealm().Addr() + caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE! AssertIsMember(caller) prop := &proposal{ @@ -78,7 +78,7 @@ func Propose(comment string, executor pproposal.Executor) int { func VoteOnProposal(idx int, option string) { assertProposalExists(idx) - caller := std.PrevRealm().Addr() + caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE! AssertIsMember(caller) prop := getProposal(idx) @@ -151,7 +151,7 @@ func Render(path string) string { output := "" for idx, prop := range proposals { - output += ufmt.Sprintf("- [/r/gov/dao:%d](%d) - %s (**%s**)(by %s)\n", idx, idx, prop.comment, string(prop.Status()), prop.author) + output += ufmt.Sprintf("- [%d](/r/gov/dao:%d) - %s (**%s**)(by %s)\n", idx, idx, prop.comment, string(prop.Status()), prop.author) } return output @@ -174,15 +174,17 @@ func Render(path string) string { } output := "" - output += ufmt.Sprintf("# Prop#%d", idx) + "\n" - output += "\n" + output += ufmt.Sprintf("# Prop #%d", idx) + output += "\n\n" output += prop.comment - output += "\n" + output += "\n\n" output += ufmt.Sprintf("Status: %s", string(prop.Status())) - output += "\n" + output += "\n\n" output += ufmt.Sprintf("Voting status: %s", prop.voter.Status(vs)) - output += "\n" + output += "\n\n" output += ufmt.Sprintf("Author: %s", string(prop.author)) + output += "\n\n" + return output } diff --git a/examples/gno.land/r/gov/dao/dao_test.gno b/examples/gno.land/r/gov/dao/dao_test.gno index d0362153715..96eaba7f5e9 100644 --- a/examples/gno.land/r/gov/dao/dao_test.gno +++ b/examples/gno.land/r/gov/dao/dao_test.gno @@ -51,12 +51,17 @@ func TestPackage(t *testing.T) { }) out = Render("0") - expected = `# Prop#0 + expected = `# Prop #0 dummy proposal + Status: active + Voting status: YES: 1, NO: 0, percent: 33, members: 3 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) @@ -69,12 +74,17 @@ Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` }) out = Render("0") - expected = `# Prop#0 + expected = `# Prop #0 dummy proposal + Status: active + Voting status: YES: 1, NO: 1, percent: 33, members: 3 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) @@ -84,12 +94,17 @@ Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` }) out = Render("0") - expected = `# Prop#0 + expected = `# Prop #0 dummy proposal + Status: accepted + Voting status: YES: 2, NO: 1, percent: 66, members: 3 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) @@ -98,12 +113,17 @@ Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` members = append(members, u4) out = Render("0") - expected = `# Prop#0 + expected = `# Prop #0 dummy proposal + Status: active + Voting status: YES: 2, NO: 1, percent: 50, members: 4 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) @@ -113,12 +133,17 @@ Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` }) out = Render("0") - expected = `# Prop#0 + expected = `# Prop #0 dummy proposal + Status: accepted + Voting status: YES: 3, NO: 1, percent: 75, members: 4 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) @@ -126,12 +151,17 @@ Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` urequire.True(t, called) out = Render("0") - expected = `# Prop#0 + expected = `# Prop #0 dummy proposal + Status: succeeded + Voting status: YES: 3, NO: 1, percent: 75, members: 4 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) @@ -145,12 +175,17 @@ Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` // 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 + expected = `# Prop #0 dummy proposal + Status: succeeded + Voting status: YES: 3, NO: 1, percent: 75, members: 4 -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr` + +Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr + +` urequire.Equal(t, expected, out) diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index 24db8801d1d..49a200fd561 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -81,34 +81,49 @@ func main() { // Output: // -- -// - [/r/gov/dao:0](0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) -// - [/r/gov/dao:1](1) - manual valset changes proposal example (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// - [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 +// # Prop #1 // // manual valset changes proposal example +// // Status: active +// // Voting status: YES: 0, NO: 0, percent: 0, members: 1 +// // Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// // -- // -- -// # Prop#1 +// # 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 +// # 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) diff --git a/examples/gno.land/r/gov/dao/prop2_filetest.gno b/examples/gno.land/r/gov/dao/prop2_filetest.gno index c6111439393..047709cc45f 100644 --- a/examples/gno.land/r/gov/dao/prop2_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop2_filetest.gno @@ -65,36 +65,51 @@ func main() { // Output: // -- -// - [/r/gov/dao:0](0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) -// - [/r/gov/dao:1](1) - post a new blogpost about govdao (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// - [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 +// # Prop #1 // // post a new blogpost about govdao +// // Status: active +// // Voting status: YES: 0, NO: 0, percent: 0, members: 1 +// // Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// // -- // -- -// # Prop#1 +// # 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 +// # 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 // diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno new file mode 100644 index 00000000000..cbc1e537e3f --- /dev/null +++ b/examples/gno.land/r/leon/config/config.gno @@ -0,0 +1,65 @@ +package config + +import ( + "errors" + "std" +) + +var ( + main std.Address // leon's main address + backup std.Address // backup address +) + +func init() { + main = "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5" +} + +func Address() std.Address { + return main +} + +func Backup() std.Address { + return backup +} + +func SetAddress(a std.Address) error { + if !a.IsValid() { + return errors.New("config: invalid address") + } + + if err := checkAuthorized(); err != nil { + return err + } + + main = a + return nil +} + +func SetBackup(a std.Address) error { + if !a.IsValid() { + return errors.New("config: invalid address") + } + + if err := checkAuthorized(); err != nil { + return err + } + + backup = a + return nil +} + +func checkAuthorized() error { + caller := std.PrevRealm().Addr() + if caller != main || caller != backup { + return errors.New("config: unauthorized") + } + + return nil +} + +func AssertAuthorized() { + caller := std.PrevRealm().Addr() + if caller != main || caller != backup { + panic("config: unauthorized") + } +} diff --git a/examples/gno.land/r/leon/config/gno.mod b/examples/gno.land/r/leon/config/gno.mod new file mode 100644 index 00000000000..e8cd5cd85b7 --- /dev/null +++ b/examples/gno.land/r/leon/config/gno.mod @@ -0,0 +1 @@ +module gno.land/r/leon/config diff --git a/examples/gno.land/r/leon/home/gno.mod b/examples/gno.land/r/leon/home/gno.mod new file mode 100644 index 00000000000..48cf64a9d0a --- /dev/null +++ b/examples/gno.land/r/leon/home/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/leon/home + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/art/gnoface v0.0.0-latest + gno.land/r/demo/art/millipede v0.0.0-latest + gno.land/r/leon/config v0.0.0-latest +) diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno new file mode 100644 index 00000000000..1f6a07e8959 --- /dev/null +++ b/examples/gno.land/r/leon/home/home.gno @@ -0,0 +1,121 @@ +package home + +import ( + "std" + "strconv" + + "gno.land/p/demo/ufmt" + + "gno.land/r/demo/art/gnoface" + "gno.land/r/demo/art/millipede" + "gno.land/r/leon/config" +) + +var ( + pfp string // link to profile picture + pfpCaption string // profile picture caption + abtMe [2]string +) + +func init() { + pfp = "https://i.imgflip.com/91vskx.jpg" + pfpCaption = "[My favourite painting & pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)" + abtMe = [2]string{ + `### About me +Hi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, +life-long learner, and sharer of knowledge.`, + `### Contributions +My contributions to gno.land can mainly be found +[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn). + +TODO import r/gh +`, + } +} + +func UpdatePFP(url, caption string) { + config.AssertAuthorized() + pfp = url + pfpCaption = caption +} + +func UpdateAboutMe(col1, col2 string) { + config.AssertAuthorized() + abtMe[0] = col1 + abtMe[1] = col2 +} + +func Render(path string) string { + out := "# Leon's Homepage\n\n" + + out += renderAboutMe() + out += renderBlogPosts() + out += "\n\n" + out += renderArt() + + return out +} + +func renderBlogPosts() string { + out := "" + //out += "## Leon's Blog Posts" + + // todo fetch blog posts authored by @leohhhn + // and render them + return out +} + +func renderAboutMe() string { + out := "
" + + out += "
\n\n" + out += ufmt.Sprintf("![my profile pic](%s)\n\n%s\n\n", pfp, pfpCaption) + out += "
\n\n" + + out += "
\n\n" + out += abtMe[0] + "\n\n" + out += "
\n\n" + + out += "
\n\n" + out += abtMe[1] + "\n\n" + out += "
\n\n" + + out += "
\n\n" + + return out +} + +func renderArt() string { + out := `
` + "\n\n" + out += "# Gno Art\n\n" + + out += "
" + + out += renderGnoFace() + out += renderMillipede() + out += "Empty spot :/" + + out += "
\n\n" + + out += "This art is dynamic; it will change with every new block.\n\n" + out += `
` + "\n" + + return out +} + +func renderGnoFace() string { + out := "
\n\n" + out += gnoface.Render(strconv.Itoa(int(std.GetHeight()))) + out += "
\n\n" + + return out +} + +func renderMillipede() string { + out := "
\n\n" + out += "Millipede\n\n" + out += "```\n" + millipede.Draw(int(std.GetHeight())%10+1) + "```\n" + out += "
\n\n" + + return out +} diff --git a/examples/gno.land/r/morgan/guestbook/admin.gno b/examples/gno.land/r/morgan/guestbook/admin.gno new file mode 100644 index 00000000000..fb7f9e1461c --- /dev/null +++ b/examples/gno.land/r/morgan/guestbook/admin.gno @@ -0,0 +1,25 @@ +package guestbook + +import ( + "gno.land/p/demo/ownable" + "gno.land/p/demo/seqid" +) + +var owner = ownable.New() + +// AdminDelete removes the guestbook message with the given ID. +// The user will still be marked as having submitted a message, so they +// won't be able to re-submit a new message. +func AdminDelete(signatureID string) { + owner.AssertCallerIsOwner() + + id, err := seqid.FromString(signatureID) + if err != nil { + panic(err) + } + idb := id.Binary() + if !guestbook.Has(idb) { + panic("signature does not exist") + } + guestbook.Remove(idb) +} diff --git a/examples/gno.land/r/morgan/guestbook/gno.mod b/examples/gno.land/r/morgan/guestbook/gno.mod new file mode 100644 index 00000000000..2591643d33d --- /dev/null +++ b/examples/gno.land/r/morgan/guestbook/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/morgan/guestbook + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest +) diff --git a/examples/gno.land/r/morgan/guestbook/guestbook.gno b/examples/gno.land/r/morgan/guestbook/guestbook.gno new file mode 100644 index 00000000000..b3a56d88397 --- /dev/null +++ b/examples/gno.land/r/morgan/guestbook/guestbook.gno @@ -0,0 +1,126 @@ +// Realm guestbook contains an implementation of a simple guestbook. +// Come and sign yourself up! +package guestbook + +import ( + "std" + "strconv" + "strings" + "time" + "unicode" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + +// Signature is a single entry in the guestbook. +type Signature struct { + Message string + Author std.Address + Time time.Time +} + +const ( + maxMessageLength = 140 + maxPerPage = 25 +) + +var ( + signatureID seqid.ID + guestbook avl.Tree // id -> Signature + hasSigned avl.Tree // address -> struct{} +) + +func init() { + Sign("You reached the end of the guestbook!") +} + +const ( + errNotAUser = "this guestbook can only be signed by users" + errAlreadySigned = "you already signed the guestbook!" + errInvalidCharacterInMessage = "invalid character in message" +) + +// Sign signs the guestbook, with the specified message. +func Sign(message string) { + prev := std.PrevRealm() + switch { + case !prev.IsUser(): + panic(errNotAUser) + case hasSigned.Has(prev.Addr().String()): + panic(errAlreadySigned) + } + message = validateMessage(message) + + guestbook.Set(signatureID.Next().Binary(), Signature{ + Message: message, + Author: prev.Addr(), + // NOTE: time.Now() will yield the "block time", which is deterministic. + Time: time.Now(), + }) + hasSigned.Set(prev.Addr().String(), struct{}{}) +} + +func validateMessage(msg string) string { + if len(msg) > maxMessageLength { + panic("Keep it brief! (max " + strconv.Itoa(maxMessageLength) + " bytes!)") + } + out := "" + for _, ch := range msg { + switch { + case unicode.IsLetter(ch), + unicode.IsNumber(ch), + unicode.IsSpace(ch), + unicode.IsPunct(ch): + out += string(ch) + default: + panic(errInvalidCharacterInMessage) + } + } + return out +} + +func Render(maxID string) string { + var bld strings.Builder + + bld.WriteString("# Guestbook 📝\n\n[Come sign the guestbook!](./guestbook?help&__func=Sign)\n\n---\n\n") + + var maxIDBinary string + if maxID != "" { + mid, err := seqid.FromString(maxID) + if err != nil { + panic(err) + } + + // AVL iteration is exclusive, so we need to decrease the ID value to get the "true" maximum. + mid-- + maxIDBinary = mid.Binary() + } + + var lastID seqid.ID + var printed int + guestbook.ReverseIterate("", maxIDBinary, func(key string, val interface{}) bool { + sig := val.(Signature) + message := strings.ReplaceAll(sig.Message, "\n", "\n> ") + bld.WriteString("> " + message + "\n>\n") + idValue, ok := seqid.FromBinary(key) + if !ok { + panic("invalid seqid id") + } + + bld.WriteString("> _Written by " + sig.Author.String() + " at " + sig.Time.Format(time.DateTime) + "_ (#" + idValue.String() + ")\n\n---\n\n") + lastID = idValue + + printed++ + // stop after exceeding limit + return printed >= maxPerPage + }) + + if printed == 0 { + bld.WriteString("No messages!") + } else if printed >= maxPerPage { + bld.WriteString("

Next page

") + } + + return bld.String() +} diff --git a/examples/gno.land/r/morgan/guestbook/guestbook_test.gno b/examples/gno.land/r/morgan/guestbook/guestbook_test.gno new file mode 100644 index 00000000000..b14fee45b42 --- /dev/null +++ b/examples/gno.land/r/morgan/guestbook/guestbook_test.gno @@ -0,0 +1,131 @@ +package guestbook + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" +) + +func TestSign(t *testing.T) { + guestbook = avl.Tree{} + hasSigned = avl.Tree{} + + std.TestSetRealm(std.NewUserRealm("g1user")) + Sign("Hello!") + + std.TestSetRealm(std.NewUserRealm("g1user2")) + Sign("Hello2!") + + res := Render("") + t.Log(res) + if !strings.Contains(res, "> Hello!\n>\n> _Written by g1user ") { + t.Error("does not contain first user's message") + } + if !strings.Contains(res, "> Hello2!\n>\n> _Written by g1user2 ") { + t.Error("does not contain second user's message") + } + if guestbook.Size() != 2 { + t.Error("invalid guestbook size") + } +} + +func TestSign_FromRealm(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) + + defer func() { + rec := recover() + if rec == nil { + t.Fatal("expected panic") + } + recString, ok := rec.(string) + if !ok { + t.Fatal("not a string", rec) + } else if recString != errNotAUser { + t.Fatal("invalid error", recString) + } + }() + Sign("Hey!") +} + +func TestSign_Double(t *testing.T) { + // Should not allow signing twice. + guestbook = avl.Tree{} + hasSigned = avl.Tree{} + + std.TestSetRealm(std.NewUserRealm("g1user")) + Sign("Hello!") + + defer func() { + rec := recover() + if rec == nil { + t.Fatal("expected panic") + } + recString, ok := rec.(string) + if !ok { + t.Error("type assertion failed", rec) + } else if recString != errAlreadySigned { + t.Error("invalid error message", recString) + } + }() + + Sign("Hello again!") +} + +func TestSign_InvalidMessage(t *testing.T) { + // Should not allow control characters in message. + guestbook = avl.Tree{} + hasSigned = avl.Tree{} + + std.TestSetRealm(std.NewUserRealm("g1user")) + + defer func() { + rec := recover() + if rec == nil { + t.Fatal("expected panic") + } + recString, ok := rec.(string) + if !ok { + t.Error("type assertion failed", rec) + } else if recString != errInvalidCharacterInMessage { + t.Error("invalid error message", recString) + } + }() + Sign("\x00Hello!") +} + +func TestAdminDelete(t *testing.T) { + const ( + userAddr std.Address = "g1user" + adminAddr std.Address = "g1admin" + ) + + guestbook = avl.Tree{} + hasSigned = avl.Tree{} + owner = ownable.NewWithAddress(adminAddr) + signatureID = 0 + + std.TestSetRealm(std.NewUserRealm(userAddr)) + + const bad = "Very Bad Message! Nyeh heh heh!" + Sign(bad) + + if rnd := Render(""); !strings.Contains(rnd, bad) { + t.Fatal("render does not contain bad message", rnd) + } + + std.TestSetRealm(std.NewUserRealm(adminAddr)) + AdminDelete(signatureID.String()) + + if rnd := Render(""); strings.Contains(rnd, bad) { + t.Error("render contains bad message", rnd) + } + if guestbook.Size() != 0 { + t.Error("invalid guestbook size") + } + if hasSigned.Size() != 1 { + t.Error("invalid hasSigned size") + } +} diff --git a/examples/gno.land/r/morgan/home/gno.mod b/examples/gno.land/r/morgan/home/gno.mod new file mode 100644 index 00000000000..573a7e139e7 --- /dev/null +++ b/examples/gno.land/r/morgan/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/morgan/home diff --git a/examples/gno.land/r/morgan/home/home.gno b/examples/gno.land/r/morgan/home/home.gno new file mode 100644 index 00000000000..33d7e0b2df7 --- /dev/null +++ b/examples/gno.land/r/morgan/home/home.gno @@ -0,0 +1,10 @@ +package home + +const staticHome = `# morgan's (gn)home + +- [📝 sign my guestbook](/r/morgan/guestbook) +` + +func Render(path string) string { + return staticHome +} 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/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/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 03ffe876519..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 @@ -2,8 +2,8 @@ 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/README.md b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/README.md deleted file mode 100644 index fa73113ef3b..00000000000 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Lazy Migration Example - -This example demonstrates lazy migrations from v1 to v2 of a data structure in Gno. - -## Notes - -Uses AVL trees, but storage can vary since public Get functions are used. - -v1 can be made pausable and readonly during migration. 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/gno.land/cmd/gnoland/config_get.go b/gno.land/cmd/gnoland/config_get.go index 1fd4027ec60..796ae9da5e9 100644 --- a/gno.land/cmd/gnoland/config_get.go +++ b/gno.land/cmd/gnoland/config_get.go @@ -36,6 +36,19 @@ func newConfigGetCmd(io commands.IO) *commands.Command { }, ) + // Add subcommand helpers + helperGen := metadataHelperGenerator{ + MetaUpdate: func(meta *commands.Metadata, inputType string) { + meta.ShortUsage = fmt.Sprintf("config get %s <%s>", meta.Name, inputType) + }, + TagNameSelector: "json", + TreeDisplay: true, + } + subs := generateSubCommandHelper(helperGen, config.Config{}, func(_ context.Context, args []string) error { + return execConfigGet(cfg, io, args) + }) + + cmd.AddSubCommands(subs...) return cmd } diff --git a/gno.land/cmd/gnoland/config_help.go b/gno.land/cmd/gnoland/config_help.go new file mode 100644 index 00000000000..97d43953bba --- /dev/null +++ b/gno.land/cmd/gnoland/config_help.go @@ -0,0 +1,127 @@ +package main + +import ( + "context" + "fmt" + "reflect" + "strings" + "unicode" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type metadataHelperGenerator struct { + // Optional callback to edit metadata + MetaUpdate func(meta *commands.Metadata, inputType string) + // Tag to select for name, if empty will use the field Name + TagNameSelector string + // Will display description with tree representation + TreeDisplay bool +} + +// generateSubCommandHelper generates subcommands based on `s` structure fields and their respective tag descriptions +func generateSubCommandHelper(gen metadataHelperGenerator, s any, exec commands.ExecMethod) []*commands.Command { + rv := reflect.ValueOf(s) + metas := gen.generateFields(rv, "", 0) + + cmds := make([]*commands.Command, len(metas)) + for i := 0; i < len(metas); i++ { + meta := metas[i] + exec := func(ctx context.Context, args []string) error { + args = append([]string{meta.Name}, args...) + return exec(ctx, args) + } + cmds[i] = commands.NewCommand(meta, nil, exec) + } + + return cmds +} + +func (g *metadataHelperGenerator) generateFields(rv reflect.Value, parent string, depth int) []commands.Metadata { + if parent != "" { + parent += "." + } + + // Unwrap pointer if needed + if rv.Kind() == reflect.Ptr { + if rv.IsNil() { + // Create a new non-nil instance of the original type that was nil + rv = reflect.New(rv.Type().Elem()) + } + rv = rv.Elem() // Dereference to struct value + } + + metas := []commands.Metadata{} + if rv.Kind() != reflect.Struct { + return metas + } + + rt := rv.Type() + for i := 0; i < rv.NumField(); i++ { + field := rt.Field(i) + if !field.IsExported() { + continue + } + + fieldValue := rv.Field(i) + name := field.Name + // Get JSON tag name + if g.TagNameSelector != "" { + name, _, _ = strings.Cut(field.Tag.Get(g.TagNameSelector), ",") + if name == "" || name == "-" { + continue + } + } + + // Recursive call for nested struct + var childs []commands.Metadata + if k := fieldValue.Kind(); k == reflect.Ptr || k == reflect.Struct { + childs = g.generateFields(fieldValue, name, depth+1) + } + + // Generate metadata + var meta commands.Metadata + + // Name + meta.Name = parent + name + + // Create a tree-like display to see nested field + if g.TreeDisplay && depth > 0 { + meta.ShortHelp += strings.Repeat(" ", depth*2) + if i == rv.NumField()-1 { + meta.ShortHelp += "└─" + } else { + meta.ShortHelp += "├─" + } + } + meta.ShortHelp += fmt.Sprintf("<%s>", field.Type) + + // Get Short/Long Help Message from comment tag + comment := field.Tag.Get("comment") + comment = strings.TrimFunc(comment, func(r rune) bool { + return unicode.IsSpace(r) || r == '#' + }) + + if comment != "" { + // Use the first line as short help + meta.ShortHelp += " " + meta.ShortHelp += strings.Split(comment, "\n")[0] + + // Display full comment as Long Help + meta.LongHelp = comment + } else { + // If the comment is empty, it mostly means that there is no help. + // Use a blank space to avoid falling back on short help. + meta.LongHelp = " " + } + + if g.MetaUpdate != nil { + g.MetaUpdate(&meta, field.Type.String()) + } + + metas = append(metas, meta) + metas = append(metas, childs...) + } + + return metas +} diff --git a/gno.land/cmd/gnoland/config_set.go b/gno.land/cmd/gnoland/config_set.go index dd171970bf6..de96aa35c7d 100644 --- a/gno.land/cmd/gnoland/config_set.go +++ b/gno.land/cmd/gnoland/config_set.go @@ -34,6 +34,18 @@ func newConfigSetCmd(io commands.IO) *commands.Command { }, ) + // Add subcommand helpers + helperGen := metadataHelperGenerator{ + MetaUpdate: func(meta *commands.Metadata, inputType string) { + meta.ShortUsage = fmt.Sprintf("config set %s <%s>", meta.Name, inputType) + }, + TagNameSelector: "json", + TreeDisplay: true, + } + cmd.AddSubCommands(generateSubCommandHelper(helperGen, config.Config{}, func(_ context.Context, args []string) error { + return execConfigEdit(cfg, io, args) + })...) + return cmd } diff --git a/gno.land/cmd/gnoland/genesis_balances_add.go b/gno.land/cmd/gnoland/genesis_balances_add.go index 4c8603c1273..f9a898715c8 100644 --- a/gno.land/cmd/gnoland/genesis_balances_add.go +++ b/gno.land/cmd/gnoland/genesis_balances_add.go @@ -10,6 +10,7 @@ import ( "os" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -57,13 +58,13 @@ func (c *balancesAddCfg) RegisterFlags(fs *flag.FlagSet) { &c.balanceSheet, "balance-sheet", "", - "the path to the balance file containing addresses in the format
=ugnot", + "the path to the balance file containing addresses in the format
="+ugnot.Denom, ) fs.Var( &c.singleEntries, "single", - "the direct balance addition in the format
=ugnot", + "the direct balance addition in the format
="+ugnot.Denom, ) fs.StringVar( @@ -167,7 +168,7 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e io.Println() for address, balance := range finalBalances { - io.Printfln("%s:%dugnot", address.String(), balance) + io.Printfln("%s:%d%s", address.String(), balance, ugnot.Denom) } return nil @@ -208,7 +209,7 @@ func getBalancesFromTransactions( } feeAmount := std.NewCoins(tx.Fee.GasFee) - if feeAmount.AmountOf("ugnot") <= 0 { + if feeAmount.AmountOf(ugnot.Denom) <= 0 { io.ErrPrintfln( "invalid gas fee amount encountered: %q", tx.Fee.GasFee.String(), @@ -223,7 +224,7 @@ func getBalancesFromTransactions( msgSend := msg.(bank.MsgSend) sendAmount := msgSend.Amount - if sendAmount.AmountOf("ugnot") <= 0 { + if sendAmount.AmountOf(ugnot.Denom) <= 0 { io.ErrPrintfln( "invalid send amount encountered: %s", msgSend.Amount.String(), @@ -248,7 +249,7 @@ func getBalancesFromTransactions( if from.IsAllLT(sendAmount) || from.IsAllLT(feeAmount) { // Account cannot cover send amount / fee // (see message above) - from = std.NewCoins(std.NewCoin("ugnot", 0)) + from = std.NewCoins(std.NewCoin(ugnot.Denom, 0)) } if from.IsAllGT(sendAmount) { diff --git a/gno.land/cmd/gnoland/genesis_balances_add_test.go b/gno.land/cmd/gnoland/genesis_balances_add_test.go index 9589bf919cc..8f2879f9c57 100644 --- a/gno.land/cmd/gnoland/genesis_balances_add_test.go +++ b/gno.land/cmd/gnoland/genesis_balances_add_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -100,16 +101,16 @@ func TestGenesis_Balances_Add(t *testing.T) { tempGenesis.Name(), } - amount := std.NewCoins(std.NewCoin("ugnot", 10)) + amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) for _, dummyKey := range dummyKeys { args = append(args, "--single") args = append( args, fmt.Sprintf( - "%s=%dugnot", + "%s=%s", dummyKey.Address().String(), - amount.AmountOf("ugnot"), + ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ), ) } @@ -158,7 +159,7 @@ func TestGenesis_Balances_Add(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) dummyKeys := getDummyKeys(t, 10) - amount := std.NewCoins(std.NewCoin("ugnot", 10)) + amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) balances := make([]string, len(dummyKeys)) @@ -167,9 +168,9 @@ func TestGenesis_Balances_Add(t *testing.T) { for index, key := range dummyKeys { balances[index] = fmt.Sprintf( - "%s=%dugnot", + "%s=%s", key.Address().String(), - amount.AmountOf("ugnot"), + ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ) } @@ -237,9 +238,9 @@ func TestGenesis_Balances_Add(t *testing.T) { var ( dummyKeys = getDummyKeys(t, 10) - amount = std.NewCoins(std.NewCoin("ugnot", 10)) - amountCoins = std.NewCoins(std.NewCoin("ugnot", 10)) - gasFee = std.NewCoin("ugnot", 1000000) + amount = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) + amountCoins = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) + gasFee = std.NewCoin(ugnot.Denom, 1000000) txs = make([]std.Tx, 0) ) @@ -316,7 +317,7 @@ func TestGenesis_Balances_Add(t *testing.T) { if index == 0 { // the first address should // have a balance of 0 - checkAmount = std.NewCoins(std.NewCoin("ugnot", 0)) + checkAmount = std.NewCoins(std.NewCoin(ugnot.Denom, 0)) } if dummyKey.Address().String() == balance.Address.String() { @@ -347,7 +348,7 @@ func TestGenesis_Balances_Add(t *testing.T) { Balances: []gnoland.Balance{ { Address: dummyKeys[0].Address(), - Amount: std.NewCoins(std.NewCoin("ugnot", 100)), + Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 100)), }, }, } @@ -364,16 +365,16 @@ func TestGenesis_Balances_Add(t *testing.T) { tempGenesis.Name(), } - amount := std.NewCoins(std.NewCoin("ugnot", 10)) + amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) for _, dummyKey := range dummyKeys { args = append(args, "--single") args = append( args, fmt.Sprintf( - "%s=%dugnot", + "%s=%s", dummyKey.Address().String(), - amount.AmountOf("ugnot"), + ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ), ) } @@ -421,9 +422,9 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) { var ( dummyKeys = getDummyKeys(t, 10) - amount = std.NewCoins(std.NewCoin("ugnot", 10)) - amountCoins = std.NewCoins(std.NewCoin("ugnot", 10)) - gasFee = std.NewCoin("ugnot", 1000000) + amount = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) + amountCoins = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) + gasFee = std.NewCoin(ugnot.Denom, 1000000) txs = make([]std.Tx, 0) ) @@ -479,7 +480,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) { var ( dummyKeys = getDummyKeys(t, 10) - amountCoins = std.NewCoins(std.NewCoin("ugnot", 10)) + amountCoins = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) gasFee = std.NewCoin("gnos", 1) // invalid fee txs = make([]std.Tx, 0) ) @@ -532,7 +533,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) { var ( dummyKeys = getDummyKeys(t, 10) amountCoins = std.NewCoins(std.NewCoin("gnogno", 10)) // invalid send amount - gasFee = std.NewCoin("ugnot", 1) + gasFee = std.NewCoin(ugnot.Denom, 1) txs = make([]std.Tx, 0) ) diff --git a/gno.land/cmd/gnoland/genesis_balances_export_test.go b/gno.land/cmd/gnoland/genesis_balances_export_test.go index ee88af4c56b..bd1f6152246 100644 --- a/gno.land/cmd/gnoland/genesis_balances_export_test.go +++ b/gno.land/cmd/gnoland/genesis_balances_export_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/testutils" @@ -18,7 +19,7 @@ func getDummyBalances(t *testing.T, count int) []gnoland.Balance { t.Helper() dummyKeys := getDummyKeys(t, count) - amount := std.NewCoins(std.NewCoin("ugnot", 10)) + amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) balances := make([]gnoland.Balance, len(dummyKeys)) diff --git a/gno.land/cmd/gnoland/genesis_balances_remove_test.go b/gno.land/cmd/gnoland/genesis_balances_remove_test.go index a8ec6ddac10..ed11836ba4d 100644 --- a/gno.land/cmd/gnoland/genesis_balances_remove_test.go +++ b/gno.land/cmd/gnoland/genesis_balances_remove_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" @@ -75,7 +76,7 @@ func TestGenesis_Balances_Remove(t *testing.T) { Balances: []gnoland.Balance{ { Address: dummyKey.Address(), - Amount: std.NewCoins(std.NewCoin("ugnot", 100)), + Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 100)), }, }, } diff --git a/gno.land/cmd/gnoland/genesis_txs_add_packages.go b/gno.land/cmd/gnoland/genesis_txs_add_packages.go index 93246eadff5..56d165c070b 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_packages.go +++ b/gno.land/cmd/gnoland/genesis_txs_add_packages.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -16,7 +17,7 @@ var errInvalidPackageDir = errors.New("invalid package directory") var ( genesisDeployAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 - genesisDeployFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) ) // newTxsAddPackagesCmd creates the genesis txs add packages subcommand diff --git a/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go b/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go index 1d49422afd1..a70446cfe6c 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go +++ b/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -30,12 +31,12 @@ func generateDummyTxs(t *testing.T, count int) []std.Tx { bank.MsgSend{ FromAddress: crypto.Address{byte(i)}, ToAddress: crypto.Address{byte((i + 1) % count)}, - Amount: std.NewCoins(std.NewCoin("ugnot", 1)), + Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 1)), }, }, Fee: std.Fee{ GasWanted: 1, - GasFee: std.NewCoin("ugnot", 1000000), + GasFee: std.NewCoin(ugnot.Denom, 1000000), }, Memo: fmt.Sprintf("tx %d", i), } diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 47de7a46283..8d111516816 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -41,6 +41,18 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { }, ) + // Add subcommand helpers + helperGen := metadataHelperGenerator{ + MetaUpdate: func(meta *commands.Metadata, inputType string) { + meta.ShortUsage = fmt.Sprintf("secrets get %s <%s>", meta.Name, inputType) + }, + TagNameSelector: "json", + TreeDisplay: false, + } + cmd.AddSubCommands(generateSubCommandHelper(helperGen, secrets{}, func(_ context.Context, args []string) error { + return execSecretsGet(cfg, args, io) + })...) + return cmd } diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index e62bce0c59f..8594e6596ce 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -1,21 +1,30 @@ # test for add package -# load hello.gno package located in $WORK directory as gno.land/r/hello -loadpkg gno.land/r/hello $WORK - ## start a new node gnoland start -## execute SayHello -gnokey maketx call -pkgpath gno.land/r/hello -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +## deploy realm +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## check output +stdout OK! +stdout 'GAS WANTED: 100000000' +stdout 'GAS USED: \d+' +stdout 'HEIGHT: \d+' +stdout 'EVENTS: \[\]' +stdout 'TX HASH: ' + +## call added realm +gnokey maketx call -pkgpath gno.land/r/$USER_ADDR_test1/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 -## compare SayHello +## check output stdout '\("hello world!" string\)' stdout OK! 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/alloc_array.txtar b/gno.land/cmd/gnoland/testdata/alloc_array.txtar new file mode 100644 index 00000000000..df9e6539297 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/alloc_array.txtar @@ -0,0 +1,16 @@ +loadpkg gno.land/r/alloc $WORK + +gnoland start + +! gnokey maketx call -pkgpath gno.land/r/alloc -func DoAlloc -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: allocation limit exceeded' + +-- alloc.gno -- +package alloc + +var buffer interface{} + +func DoAlloc() { + var arr [1_000_000_000_000_000]byte + buffer = arr +} diff --git a/gno.land/cmd/gnoland/testdata/alloc_byte_slice.txtar b/gno.land/cmd/gnoland/testdata/alloc_byte_slice.txtar new file mode 100644 index 00000000000..99b0b85e97d --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/alloc_byte_slice.txtar @@ -0,0 +1,16 @@ +loadpkg gno.land/r/alloc $WORK + +gnoland start + +! gnokey maketx call -pkgpath gno.land/r/alloc -func DoAlloc -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: allocation limit exceeded' + +-- alloc.gno -- +package alloc + +var buffer []byte + +func DoAlloc() { + buffer := make([]byte, 1_000_000_000_000) + buffer[1] = 'a' +} diff --git a/gno.land/cmd/gnoland/testdata/alloc_slice.txtar b/gno.land/cmd/gnoland/testdata/alloc_slice.txtar new file mode 100644 index 00000000000..21a4d28d90d --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/alloc_slice.txtar @@ -0,0 +1,16 @@ +loadpkg gno.land/r/alloc $WORK + +gnoland start + +! gnokey maketx call -pkgpath gno.land/r/alloc -func DoAlloc -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: allocation limit exceeded' + +-- alloc.gno -- +package alloc + +var buffer []int + +func DoAlloc() { + buffer := make([]int, 1_000_000_000_000) + buffer[1] = 1 +} diff --git a/gno.land/cmd/gnoland/testdata/event_callback.txtar b/gno.land/cmd/gnoland/testdata/event_callback.txtar index 3606cfae81e..86377dfee6b 100644 --- a/gno.land/cmd/gnoland/testdata/event_callback.txtar +++ b/gno.land/cmd/gnoland/testdata/event_callback.txtar @@ -10,7 +10,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' 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 2bb572271ad..44388be502d 100644 --- a/gno.land/cmd/gnoland/testdata/event_defer_callback_loop.txtar +++ b/gno.land/cmd/gnoland/testdata/event_defer_callback_loop.txtar @@ -10,6 +10,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' 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 63eb26c47d8..be7f501f255 100644 --- a/gno.land/cmd/gnoland/testdata/event_for_statement.txtar +++ b/gno.land/cmd/gnoland/testdata/event_for_statement.txtar @@ -10,6 +10,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' 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! @@ -17,7 +18,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: [0-9]+' stdout 'HEIGHT: [0-9]+' 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_normal.txtar b/gno.land/cmd/gnoland/testdata/event_normal.txtar index b2792c07cf3..b2d6c01f3ec 100644 --- a/gno.land/cmd/gnoland/testdata/event_normal.txtar +++ b/gno.land/cmd/gnoland/testdata/event_normal.txtar @@ -10,6 +10,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: \d+' stdout 'HEIGHT: \d+' 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! @@ -17,6 +18,7 @@ stdout 'GAS WANTED: 2000000' stdout 'GAS USED: \d+' stdout 'HEIGHT: \d+' 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/gnoweb_airgapped.txtar b/gno.land/cmd/gnoland/testdata/gnoweb_airgapped.txtar index e1ed4a63915..3ed35a1b1d3 100644 --- a/gno.land/cmd/gnoland/testdata/gnoweb_airgapped.txtar +++ b/gno.land/cmd/gnoland/testdata/gnoweb_airgapped.txtar @@ -8,8 +8,18 @@ loadpkg gno.land/r/demo/echo gnoland start # Query account -gnokey query auth/accounts/$USER_ADDR_test1 -cmp stdout query.stdout.golden +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 @@ -23,17 +33,6 @@ gnokey broadcast $WORK/call.tx stdout '("HELLO" string)' stdout 'GAS WANTED: 2000000' --- query.stdout.golden -- -height: 0 -data: { - "BaseAccount": { - "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - "coins": "9999999000000ugnot", - "public_key": null, - "account_number": "0", - "sequence": "0" - } -} -- sign.stdout.golden -- Tx successfully signed and saved to $WORK/call.tx diff --git a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar index 38b0c8fe865..95bd48c0144 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar @@ -101,4 +101,3 @@ import ( func Call(s string) { base64.StdEncoding.DecodeString("hey") } - diff --git a/gno.land/cmd/gnoland/testdata/panic.txtar b/gno.land/cmd/gnoland/testdata/panic.txtar new file mode 100644 index 00000000000..2b964d80751 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/panic.txtar @@ -0,0 +1,27 @@ +# test panic + +loadpkg gno.land/r/demo/panic $WORK + +# start a new node +gnoland start + + +! gnokey maketx call -pkgpath gno.land/r/demo/panic --func Trigger --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 + +stderr 'p\\(\)' +stderr 'gno.land/r/demo/panic/panic.gno:5' +stderr 'pkg\\.Trigger\(\)' +stderr 'gno.land/r/demo/panic/panic.gno:9' + +-- panic.gno -- +package main + +func p() { + i := "here" + panic(i) +} + +func Trigger() { + p() +} + diff --git a/gno.land/cmd/gnoland/testdata/restart_missing_type.txtar b/gno.land/cmd/gnoland/testdata/restart_missing_type.txtar new file mode 100644 index 00000000000..7592693eeff --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/restart_missing_type.txtar @@ -0,0 +1,202 @@ +# This txtar is a regression test for a bug, whereby a type is committed to +# the defaultStore.cacheTypes map, but not to the underlying store (due to a +# failing transaction). +# For more information: https://github.com/gnolang/gno/pull/2605 +loadpkg gno.land/p/demo/avl +gnoland start + +gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 0 test1 +! gnokey broadcast $WORK/tx1.tx +stderr 'out of gas' + +gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 1 test1 +gnokey broadcast $WORK/tx2.tx +stdout 'OK!' + +gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 2 test1 +gnokey broadcast $WORK/tx3.tx +stdout 'OK!' + +gnoland restart + +-- tx1.tx -- +{ + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "zentasktic", + "path": "gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic", + "files": [ + { + "name": "README.md", + "body": "# ZenTasktic Core\n\nA basic, minimalisitc Asess-Decide-Do implementations as `p/zentasktic`. The diagram below shows a simplified ADD workflow.\n\n![ZenTasktic](ZenTasktic-framework.png)\n\nThis implementation will expose all the basic features of the framework: tasks & projects with complete workflows. Ideally, this should offer all the necessary building blocks for any other custom implementation.\n\n## Object Definitions and Default Values\n\nAs an unopinionated ADD workflow, `zentastic_core` defines the following objects:\n\n- Realm\n\nRealms act like containers for tasks & projects during their journey from Assess to Do, via Decide. Each realm has a certain restrictions, e.g. a task's Body can only be edited in Assess, a Context, Due date and Alert can only be added in Decide, etc.\n\nIf someone observes different realms, there is support for adding and removing arbitrary Realms.\n\n_note: the Ids between 1 and 4 are reserved for: 1-Assess, 2-Decide, 3-Do, 4-Collection. Trying to add or remove such a Realm will raise an error._\n\n\nRealm data definition:\n\n```\ntype Realm struct {\n\tId \t\t\tstring `json:\"realmId\"`\n\tName \t\tstring `json:\"realmName\"`\n}\n```\n\n- Task\n\nA task is the minimal data structure in ZenTasktic, with the following definition:\n\n```\ntype Task struct {\n\tId \t\t\tstring `json:\"taskId\"`\n\tProjectId \tstring `json:\"taskProjectId\"`\n\tContextId\tstring `json:\"taskContextId\"`\n\tRealmId \tstring `json:\"taskRealmId\"`\n\tBody \t\tstring `json:\"taskBody\"`\n\tDue\t\t\tstring `json:\"taskDue\"`\n\tAlert\t\tstring `json:\"taskAlert\"`\n}\n```\n\n- Project\n\nProjects are unopinionated collections of Tasks. A Task in a Project can be in any Realm, but the restrictions are propagated upwards to the Project: e.g. if a Task is marked as 'done' in the Do realm (namely changing its RealmId property to \"1\", Assess, or \"4\" Collection), and the rest of the tasks are not, the Project cannot be moved back to Decide or Asses, all Tasks must have consisted RealmId properties.\n\nA Task can be arbitrarily added to, removed from and moved to another Project.\n\nProject data definition:\n\n\n```\ntype Project struct {\n\tId \t\t\tstring `json:\"projectId\"`\n\tContextId\tstring `json:\"projectContextId\"`\n\tRealmId \tstring `json:\"projectRealmId\"`\n\tTasks\t\t[]Task `json:\"projectTasks\"`\n\tBody \t\tstring `json:\"projectBody\"`\n\tDue\t\t\tstring `json:\"ProjectDue\"`\n}\n```\n\n\n- Context\n\nContexts act as tags, grouping together Tasks and Project, e.g. \"Backend\", \"Frontend\", \"Marketing\". Contexts have no defaults and can be added or removed arbitrarily.\n\nContext data definition:\n\n```\ntype Context struct {\n\tId \t\t\tstring `json:\"contextId\"`\n\tName \t\tstring `json:\"contextName\"`\n}\n```\n\n- Collection\n\nCollections are intended as an agnostic storage for Tasks & Projects which are either not ready to be Assessed, or they have been already marked as done, and, for whatever reason, they need to be kept in the system. There is a special Realm Id for Collections, \"4\", although technically they are not part of the Assess-Decide-Do workflow.\n\nCollection data definition:\n\n```\ntype Collection struct {\n\tId \t\t\tstring `json:\"collectionId\"`\n\tRealmId \tstring `json:\"collectionRealmId\"`\n\tName \t\tstring `json:\"collectionName\"`\n\tTasks\t\t[]Task `json:\"collectionTasks\"`\n\tProjects\t[]Project `json:\"collectionProjects\"`\n}\n```\n\n- ObjectPath\n\nObjectPaths are minimalistic representations of the journey taken by a Task or a Project in the Assess-Decide-Do workflow. By recording their movement between various Realms, one can extract their `ZenStatus`, e.g., if a Task has been moved many times between Assess and Decide, never making it to Do, we can infer the following:\n-- either the Assess part was incomplete\n-- the resources needed for that Task are not yet ready\n\nObjectPath data definition:\n\n```\ntype ObjectPath struct {\n\tObjectType\tstring `json:\"objectType\"` // Task, Project\n\tId \t\t\tstring `json:\"id\"` // this is the Id of the object moved, Task, Project\n\tRealmId \tstring `json:\"realmId\"`\n}\n```\n\n_note: the core implementation offers the basic adding and retrieving functionality, but it's up to the client realm using the `zentasktic` package to call them when an object is moved from one Realm to another._\n\n## Example Workflow\n\n```\npackage example_zentasktic\n\nimport \"gno.land/p/demo/zentasktic\"\n\nvar ztm *zentasktic.ZTaskManager\nvar zpm *zentasktic.ZProjectManager\nvar zrm *zentasktic.ZRealmManager\nvar zcm *zentasktic.ZContextManager\nvar zcl *zentasktic.ZCollectionManager\nvar zom *zentasktic.ZObjectPathManager\n\nfunc init() {\n ztm = zentasktic.NewZTaskManager()\n zpm = zentasktic.NewZProjectManager()\n\tzrm = zentasktic.NewZRealmManager()\n\tzcm = zentasktic.NewZContextManager()\n\tzcl = zentasktic.NewZCollectionManager()\n\tzom = zentasktic.NewZObjectPathManager()\n}\n\n// initializing a task, assuming we get the value POSTed by some call to the current realm\n\nnewTask := zentasktic.Task{Id: \"20\", Body: \"Buy milk\"}\nztm.AddTask(newTask)\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"1\"}\nzom.AddPath(taskPath)\n...\n\neditedTask := zentasktic.Task{Id: \"20\", Body: \"Buy fresh milk\"}\nztm.EditTask(editedTask)\n\n...\n\n// moving it to Decide\n\nztm.MoveTaskToRealm(\"20\", \"2\")\n\n// adding context, due date and alert, assuming they're received from other calls\n\nshoppingContext := zcm.GetContextById(\"2\")\n\ncerr := zcm.AddContextToTask(ztm, shoppingContext, editedTask)\n\nderr := ztm.SetTaskDueDate(editedTask.Id, \"2024-04-10\")\nnow := time.Now() // replace with the actual time of the alert\nalertTime := now.Format(\"2006-01-02 15:04:05\")\naerr := ztm.SetTaskAlert(editedTask.Id, alertTime)\n\n...\n\n// move the Task to Do\n\nztm.MoveTaskToRealm(editedTask.Id, \"2\")\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"2\"}\nzom.AddPath(taskPath)\n\n// after the task is done, we sent it back to Assess\n\nztm.MoveTaskToRealm(editedTask.Id,\"1\")\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"1\"}\nzom.AddPath(taskPath)\n\n// from here, we can add it to a collection\n\nmyCollection := zcm.GetCollectionById(\"1\")\n\nzcm.AddTaskToCollection(ztm, myCollection, editedTask)\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"4\"}\nzom.AddPath(taskPath)\n\n```\n\nAll tests are in the `*_test.gno` files, e.g. `tasks_test.gno`, `projects_test.gno`, etc." + }, + { + "name": "collections.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\n\ntype Collection struct {\n\tId \t\t\tstring `json:\"collectionId\"`\n\tRealmId \tstring `json:\"collectionRealmId\"`\n\tName \t\tstring `json:\"collectionName\"`\n\tTasks\t\t[]Task `json:\"collectionTasks\"`\n\tProjects\t[]Project `json:\"collectionProjects\"`\n}\n\ntype ZCollectionManager struct {\n\tCollections *avl.Tree \n\tCollectionTasks *avl.Tree\n\tCollectionProjects *avl.Tree \n}\n\nfunc NewZCollectionManager() *ZCollectionManager {\n return &ZCollectionManager{\n Collections: avl.NewTree(),\n CollectionTasks: avl.NewTree(),\n CollectionProjects: avl.NewTree(),\n }\n}\n\n\n// actions\n\nfunc (zcolm *ZCollectionManager) AddCollection(c Collection) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif exist {\n\t\t\treturn ErrCollectionIdAlreadyExists\n\t\t}\n\t}\n\tzcolm.Collections.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) EditCollection(c Collection) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\t\n\tzcolm.Collections.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) RemoveCollection(c Collection) (err error) {\n // implementation\n if zcolm.Collections.Size() != 0 {\n collectionInterface, exist := zcolm.Collections.Get(c.Id)\n if !exist {\n return ErrCollectionIdNotFound\n }\n collection := collectionInterface.(Collection)\n\n _, removed := zcolm.Collections.Remove(collection.Id)\n if !removed {\n return ErrCollectionNotRemoved\n }\n\n if zcolm.CollectionTasks.Size() != 0 {\n _, removedTasks := zcolm.CollectionTasks.Remove(collection.Id)\n if !removedTasks {\n return ErrCollectionNotRemoved\n }\t\n }\n\n if zcolm.CollectionProjects.Size() != 0 {\n _, removedProjects := zcolm.CollectionProjects.Remove(collection.Id)\n if !removedProjects {\n return ErrCollectionNotRemoved\n }\t\n }\n }\n return nil\n}\n\n\nfunc (zcolm *ZCollectionManager) AddProjectToCollection(zpm *ZProjectManager, c Collection, p Project) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionProjects, texist := zcolm.CollectionProjects.Get(c.Id)\n\tif !texist {\n\t\t// If the collections has no projects yet, initialize the slice.\n\t\texistingCollectionProjects = []Project{}\n\t} else {\n\t\tprojects, ok := existingCollectionProjects.([]Project)\n\t\tif !ok {\n\t\t\treturn ErrCollectionsProjectsNotFound\n\t\t}\n\t\texistingCollectionProjects = projects\n\t}\n\tp.RealmId = \"4\"\n\tif err := zpm.EditProject(p); err != nil {\n\t\treturn err\n\t}\n\tupdatedProjects := append(existingCollectionProjects.([]Project), p)\n\tzcolm.CollectionProjects.Set(c.Id, updatedProjects)\n\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) AddTaskToCollection(ztm *ZTaskManager, c Collection, t Task) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionTasks, texist := zcolm.CollectionTasks.Get(c.Id)\n\tif !texist {\n\t\t// If the collections has no tasks yet, initialize the slice.\n\t\texistingCollectionTasks = []Task{}\n\t} else {\n\t\ttasks, ok := existingCollectionTasks.([]Task)\n\t\tif !ok {\n\t\t\treturn ErrCollectionsTasksNotFound\n\t\t}\n\t\texistingCollectionTasks = tasks\n\t}\n\tt.RealmId = \"4\"\n\tif err := ztm.EditTask(t); err != nil {\n\t\treturn err\n\t}\n\tupdatedTasks := append(existingCollectionTasks.([]Task), t)\n\tzcolm.CollectionTasks.Set(c.Id, updatedTasks)\n\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) RemoveProjectFromCollection(zpm *ZProjectManager, c Collection, p Project) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionProjects, texist := zcolm.CollectionProjects.Get(c.Id)\n\tif !texist {\n\t\t// If the collection has no projects yet, return appropriate error\n\t\treturn ErrCollectionsProjectsNotFound\n\t}\n\n\t// Find the index of the project to be removed.\n\tvar index int = -1\n\tfor i, project := range existingCollectionProjects.([]Project) {\n\t\tif project.Id == p.Id {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// If the project was found, we remove it from the slice.\n\tif index != -1 {\n\t\t// by default we send it back to Assess\n\t\tp.RealmId = \"1\"\n\t\tzpm.EditProject(p)\n\t\texistingCollectionProjects = append(existingCollectionProjects.([]Project)[:index], existingCollectionProjects.([]Project)[index+1:]...)\n\t} else {\n\t\t// Project not found in the collection\n\t\treturn ErrProjectByIdNotFound \n\t}\n\tzcolm.CollectionProjects.Set(c.Id, existingCollectionProjects)\n\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) RemoveTaskFromCollection(ztm *ZTaskManager, c Collection, t Task) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionTasks, texist := zcolm.CollectionTasks.Get(c.Id)\n\tif !texist {\n\t\t// If the collection has no tasks yet, return appropriate error\n\t\treturn ErrCollectionsTasksNotFound\n\t}\n\n\t// Find the index of the task to be removed.\n\tvar index int = -1\n\tfor i, task := range existingCollectionTasks.([]Task) {\n\t\tif task.Id == t.Id {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// If the task was found, we remove it from the slice.\n\tif index != -1 {\n\t\t// by default, we send the task to Assess\n\t\tt.RealmId = \"1\"\n\t\tztm.EditTask(t)\n\t\texistingCollectionTasks = append(existingCollectionTasks.([]Task)[:index], existingCollectionTasks.([]Task)[index+1:]...)\n\t} else {\n\t\t// Task not found in the collection\n\t\treturn ErrTaskByIdNotFound \n\t}\n\tzcolm.CollectionTasks.Set(c.Id, existingCollectionTasks)\n\n\treturn nil\n}\n\n// getters\n\nfunc (zcolm *ZCollectionManager) GetCollectionById(collectionId string) (Collection, error) {\n if zcolm.Collections.Size() != 0 {\n cInterface, exist := zcolm.Collections.Get(collectionId)\n if exist {\n collection := cInterface.(Collection)\n // look for collection Tasks, Projects\n existingCollectionTasks, texist := zcolm.CollectionTasks.Get(collectionId)\n if texist {\n collection.Tasks = existingCollectionTasks.([]Task)\n }\n existingCollectionProjects, pexist := zcolm.CollectionProjects.Get(collectionId)\n if pexist {\n collection.Projects = existingCollectionProjects.([]Project)\n }\n return collection, nil\n }\n return Collection{}, ErrCollectionByIdNotFound\n }\n return Collection{}, ErrCollectionByIdNotFound\n}\n\nfunc (zcolm *ZCollectionManager) GetCollectionTasks(c Collection) (tasks []Task, err error) {\n\t\n\tif zcolm.CollectionTasks.Size() != 0 {\n\t\ttask, exist := zcolm.CollectionTasks.Get(c.Id)\n\t\tif !exist {\n\t\t\t// if there's no record in CollectionTasks, we don't have to return anything\n\t\t\treturn nil, ErrCollectionsTasksNotFound\n\t\t} else {\n\t\t\t// type assertion to convert interface{} to []Task\n\t\t\texistingCollectionTasks, ok := task.([]Task)\n\t\t\tif !ok {\n\t\t\t\treturn nil, ErrTaskFailedToAssert\n\t\t\t}\n\t\t\treturn existingCollectionTasks, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc (zcolm *ZCollectionManager) GetCollectionProjects(c Collection) (projects []Project, err error) {\n\t\n\tif zcolm.CollectionProjects.Size() != 0 {\n\t\tproject, exist := zcolm.CollectionProjects.Get(c.Id)\n\t\tif !exist {\n\t\t\t// if there's no record in CollectionProjets, we don't have to return anything\n\t\t\treturn nil, ErrCollectionsProjectsNotFound\n\t\t} else {\n\t\t\t// type assertion to convert interface{} to []Projet\n\t\t\texistingCollectionProjects, ok := project.([]Project)\n\t\t\tif !ok {\n\t\t\t\treturn nil, ErrProjectFailedToAssert\n\t\t\t}\n\t\t\treturn existingCollectionProjects, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc (zcolm *ZCollectionManager) GetAllCollections() (collections string, err error) {\n\t// implementation\n\tvar allCollections []Collection\n\t\n\t// Iterate over the Collections AVL tree to collect all Project objects.\n\t\n\tzcolm.Collections.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif collection, ok := value.(Collection); ok {\n\t\t\t// get collection tasks, if any\n\t\t\tcollectionTasks, _ := zcolm.GetCollectionTasks(collection)\n\t\t\tif collectionTasks != nil {\n\t\t\t\tcollection.Tasks = collectionTasks\n\t\t\t}\n\t\t\t// get collection prokects, if any\n\t\t\tcollectionProjects, _ := zcolm.GetCollectionProjects(collection)\n\t\t\tif collectionProjects != nil {\n\t\t\t\tcollection.Projects = collectionProjects\n\t\t\t}\n\t\t\tallCollections = append(allCollections, collection)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a CollectionsObject with all collected tasks.\n\tcollectionsObject := CollectionsObject{\n\t\tCollections: allCollections,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the collections into JSON.\n\tmarshalledCollections, merr := collectionsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\", merr\n\t} \n\treturn string(marshalledCollections), nil\n} " + }, + { + "name": "collections_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n/*\n\nfunc Test_AddCollection(t *testing.T) {\n \n collection := Collection{Id: \"1\", RealmId: \"4\", Name: \"First collection\",}\n\n // Test adding a collection successfully.\n err := collection.AddCollection()\n if err != nil {\n t.Errorf(\"Failed to add collection: %v\", err)\n }\n\n // Test adding a duplicate task.\n cerr := collection.AddCollection()\n if cerr != ErrCollectionIdAlreadyExists {\n t.Errorf(\"Expected ErrCollectionIdAlreadyExists, got %v\", cerr)\n }\n}\n\nfunc Test_RemoveCollection(t *testing.T) {\n \n collection := Collection{Id: \"20\", RealmId: \"4\", Name: \"Removable collection\",}\n\n // Test adding a collection successfully.\n err := collection.AddCollection()\n if err != nil {\n t.Errorf(\"Failed to add collection: %v\", err)\n }\n\n retrievedCollection, rerr := GetCollectionById(collection.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added collection\")\n }\n\n // Test removing a collection\n terr := retrievedCollection.RemoveCollection()\n if terr != ErrCollectionNotRemoved {\n t.Errorf(\"Expected ErrCollectionNotRemoved, got %v\", terr)\n }\n}\n\nfunc Test_EditCollection(t *testing.T) {\n \n collection := Collection{Id: \"2\", RealmId: \"4\", Name: \"Second collection\",}\n\n // Test adding a collection successfully.\n err := collection.AddCollection()\n if err != nil {\n t.Errorf(\"Failed to add collection: %v\", err)\n }\n\n // Test editing the collection\n editedCollection := Collection{Id: collection.Id, RealmId: collection.RealmId, Name: \"Edited collection\",}\n cerr := editedCollection.EditCollection()\n if cerr != nil {\n t.Errorf(\"Failed to edit the collection\")\n }\n\n retrievedCollection, _ := GetCollectionById(editedCollection.Id)\n if retrievedCollection.Name != \"Edited collection\" {\n t.Errorf(\"Collection was not edited\")\n }\n}\n\nfunc Test_AddProjectToCollection(t *testing.T){\n // Example Collection and Projects\n col := Collection{Id: \"1\", Name: \"First collection\", RealmId: \"4\",}\n prj := Project{Id: \"10\", Body: \"Project 10\", RealmId: \"1\",}\n\n Collections.Set(col.Id, col) // Mock existing collections\n\n tests := []struct {\n name string\n collection Collection\n project Project\n wantErr bool\n errMsg error\n }{\n {\n name: \"Attach to existing collection\",\n collection: col,\n project: prj,\n wantErr: false,\n },\n {\n name: \"Attach to non-existing collection\",\n collection: Collection{Id: \"200\", Name: \"Collection 200\", RealmId: \"4\",},\n project: prj,\n wantErr: true,\n errMsg: ErrCollectionIdNotFound,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := tt.collection.AddProjectToCollection(tt.project)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"AddProjectToCollection() error = %v, wantErr %v\", err, tt.wantErr)\n }\n if tt.wantErr && err != tt.errMsg {\n t.Errorf(\"AddProjectToCollection() error = %v, expected %v\", err, tt.errMsg)\n }\n\n // For successful attach, verify the project is added to the collection's tasks.\n if !tt.wantErr {\n projects, exist := CollectionProjects.Get(tt.collection.Id)\n if !exist || len(projects.([]Project)) == 0 {\n t.Errorf(\"Project was not added to the collection\")\n } else {\n found := false\n for _, project := range projects.([]Project) {\n if project.Id == tt.project.Id {\n found = true\n break\n }\n }\n if !found {\n t.Errorf(\"Project was not attached to the collection\")\n }\n }\n }\n })\n }\n}\n\nfunc Test_AddTaskToCollection(t *testing.T){\n // Example Collection and Tasks\n col := Collection{Id: \"2\", Name: \"Second Collection\", RealmId: \"4\",}\n tsk := Task{Id: \"30\", Body: \"Task 30\", RealmId: \"1\",}\n\n Collections.Set(col.Id, col) // Mock existing collections\n\n tests := []struct {\n name string\n collection Collection\n task Task\n wantErr bool\n errMsg error\n }{\n {\n name: \"Attach to existing collection\",\n collection: col,\n task: tsk,\n wantErr: false,\n },\n {\n name: \"Attach to non-existing collection\",\n collection: Collection{Id: \"210\", Name: \"Collection 210\", RealmId: \"4\",},\n task: tsk,\n wantErr: true,\n errMsg: ErrCollectionIdNotFound,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := tt.collection.AddTaskToCollection(tt.task)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"AddTaskToCollection() error = %v, wantErr %v\", err, tt.wantErr)\n }\n if tt.wantErr && err != tt.errMsg {\n t.Errorf(\"AddTaskToCollection() error = %v, expected %v\", err, tt.errMsg)\n }\n\n // For successful attach, verify the task is added to the collection's tasks.\n if !tt.wantErr {\n tasks, exist := CollectionTasks.Get(tt.collection.Id)\n if !exist || len(tasks.([]Task)) == 0 {\n t.Errorf(\"Task was not added to the collection\")\n } else {\n found := false\n for _, task := range tasks.([]Task) {\n if task.Id == tt.task.Id {\n found = true\n break\n }\n }\n if !found {\n t.Errorf(\"Task was not attached to the collection\")\n }\n }\n }\n })\n }\n}\n\nfunc Test_RemoveProjectFromCollection(t *testing.T){\n // Setup:\n\tcollection := Collection{Id: \"300\", Name: \"Collection 300\",}\n\tproject1 := Project{Id: \"21\", Body: \"Project 21\", RealmId: \"1\",}\n\tproject2 := Project{Id: \"22\", Body: \"Project 22\", RealmId: \"1\",}\n\n collection.AddCollection()\n project1.AddProject()\n project2.AddProject()\n collection.AddProjectToCollection(project1)\n collection.AddProjectToCollection(project2)\n\n\ttests := []struct {\n\t\tname string\n\t\tproject Project\n\t\tcollection Collection\n\t\twantErr bool\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname: \"Remove existing project from collection\",\n\t\t\tproject: project1,\n\t\t\tcollection: collection,\n\t\t\twantErr: false,\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove project from non-existing collection\",\n\t\t\tproject: project1,\n\t\t\tcollection: Collection{Id: \"nonexistent\"},\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrCollectionIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove non-existing project from collection\",\n\t\t\tproject: Project{Id: \"nonexistent\"},\n\t\t\tcollection: collection,\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrProjectByIdNotFound,\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.collection.RemoveProjectFromCollection(tt.project)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil || err != tt.expectedErr {\n\t\t\t\t\tt.Errorf(\"%s: expected error %v, got %v\", tt.name, tt.expectedErr, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: unexpected error: %v\", tt.name, err)\n\t\t\t\t}\n\n\t\t\t\t// For successful removal, verify the project is no longer part of the collection's projects\n\t\t\t\tif !tt.wantErr {\n\t\t\t\t\tprojects, _ := CollectionProjects.Get(tt.collection.Id)\n\t\t\t\t\tfor _, project := range projects.([]Project) {\n\t\t\t\t\t\tif project.Id == tt.project.Id {\n\t\t\t\t\t\t\tt.Errorf(\"%s: project was not detached from the collection\", tt.name)\n\t\t\t\t\t\t\tbreak\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}\n}\n\nfunc Test_RemoveTaskFromCollection(t *testing.T){\n // setup, re-using parts from Test_AddTaskToCollection\n\tcollection := Collection{Id: \"40\", Name: \"Collection 40\",}\n task1 := Task{Id: \"40\", Body: \"Task 40\", RealmId: \"1\",}\n\n collection.AddCollection()\n task1.AddTask()\n collection.AddTaskToCollection(task1)\n\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\tcollection Collection\n\t\twantErr bool\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname: \"Remove existing task from collection\",\n\t\t\ttask: task1,\n\t\t\tcollection: collection,\n\t\t\twantErr: false,\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove task from non-existing collection\",\n\t\t\ttask: task1,\n\t\t\tcollection: Collection{Id: \"nonexistent\"},\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrCollectionIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove non-existing task from collection\",\n\t\t\ttask: Task{Id: \"nonexistent\"},\n\t\t\tcollection: collection,\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrTaskByIdNotFound,\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.collection.RemoveTaskFromCollection(tt.task)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil || err != tt.expectedErr {\n\t\t\t\t\tt.Errorf(\"%s: expected error %v, got %v\", tt.name, tt.expectedErr, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: unexpected error: %v\", tt.name, err)\n\t\t\t\t}\n\n\t\t\t\t// For successful removal, verify the task is no longer part of the collection's tasks\n\t\t\t\tif !tt.wantErr {\n\t\t\t\t\ttasks, _ := CollectionTasks.Get(tt.collection.Id)\n\t\t\t\t\tfor _, task := range tasks.([]Task) {\n\t\t\t\t\t\tif task.Id == tt.task.Id {\n\t\t\t\t\t\t\tt.Errorf(\"%s: task was not detached from the collection\", tt.name)\n\t\t\t\t\t\t\tbreak\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}\n}\n\nfunc Test_GetCollectionById(t *testing.T){\n // test getting a non-existing collection\n nonCollection, err := GetCollectionById(\"0\")\n if err != ErrCollectionByIdNotFound {\n t.Fatalf(\"Expected ErrCollectionByIdNotFound, got: %v\", err)\n }\n\n // test getting the correct collection by id\n correctCollection, err := GetCollectionById(\"1\")\n if err != nil {\n t.Fatalf(\"Failed to get collection by id, error: %v\", err)\n }\n\n if correctCollection.Name != \"First collection\" {\n t.Fatalf(\"Got the wrong collection, with name: %v\", correctCollection.Name)\n }\n}\n\nfunc Test_GetCollectionTasks(t *testing.T) {\n // retrieving objects based on these mocks\n //col := Collection{Id: \"2\", Name: \"Second Collection\", RealmId: \"4\",}\n tsk := Task{Id: \"30\", Body: \"Task 30\", RealmId: \"1\",}\n\n collection, cerr := GetCollectionById(\"2\")\n if cerr != nil {\n t.Errorf(\"GetCollectionById() failed, %v\", cerr)\n }\n\n collectionTasks, pterr := collection.GetCollectionTasks()\n if len(collectionTasks) == 0 {\n t.Errorf(\"GetCollectionTasks() failed, %v\", pterr)\n }\n\n // test detaching from an existing collection\n dtterr := collection.RemoveTaskFromCollection(tsk)\n if dtterr != nil {\n t.Errorf(\"RemoveTaskFromCollection() failed, %v\", dtterr)\n }\n\n collectionWithNoTasks, pterr := collection.GetCollectionTasks()\n if len(collectionWithNoTasks) != 0 {\n t.Errorf(\"GetCollectionTasks() after detach failed, %v\", pterr)\n }\n\n // add task back to collection, for tests mockup integrity\n collection.AddTaskToCollection(tsk)\n}\n\nfunc Test_GetCollectionProjects(t *testing.T) {\n // retrieving objects based on these mocks\n //col := Collection{Id: \"1\", Name: \"First Collection\", RealmId: \"4\",}\n prj := Project{Id: \"10\", Body: \"Project 10\", RealmId: \"2\", ContextId: \"2\", Due: \"2024-01-01\"}\n\n collection, cerr := GetCollectionById(\"1\")\n if cerr != nil {\n t.Errorf(\"GetCollectionById() failed, %v\", cerr)\n }\n\n collectionProjects, pterr := collection.GetCollectionProjects()\n if len(collectionProjects) == 0 {\n t.Errorf(\"GetCollectionProjects() failed, %v\", pterr)\n }\n\n // test detaching from an existing collection\n dtterr := collection.RemoveProjectFromCollection(prj)\n if dtterr != nil {\n t.Errorf(\"RemoveProjectFromCollection() failed, %v\", dtterr)\n }\n\n collectionWithNoProjects, pterr := collection.GetCollectionProjects()\n if len(collectionWithNoProjects) != 0 {\n t.Errorf(\"GetCollectionProjects() after detach failed, %v\", pterr)\n }\n\n // add project back to collection, for tests mockup integrity\n collection.AddProjectToCollection(prj)\n}\n\nfunc Test_GetAllCollections(t *testing.T){\n // mocking the collections based on previous tests\n // TODO: add isolation?\n knownCollections := []Collection{\n {\n Id: \"1\",\n RealmId: \"4\",\n Name: \"First collection\",\n Tasks: nil, \n Projects: []Project{\n {\n Id: \"10\",\n ContextId: \"2\",\n RealmId: \"4\",\n Tasks: nil, \n Body: \"Project 10\",\n Due: \"2024-01-01\",\n },\n },\n },\n {\n Id: \"2\",\n RealmId: \"4\",\n Name: \"Second Collection\",\n Tasks: []Task{\n {\n Id:\"30\",\n ProjectId:\"\",\n ContextId:\"\",\n RealmId:\"4\",\n Body:\"Task 30\",\n Due:\"\",\n Alert:\"\",\n },\n },\n Projects: nil, \n },\n {\n Id:\"20\",\n RealmId:\"4\",\n Name:\"Removable collection\",\n Tasks: nil,\n Projects: nil,\n },\n {\n Id: \"300\",\n Name: \"Collection 300\",\n Tasks: nil, \n Projects: []Project {\n {\n Id:\"22\",\n ContextId:\"\",\n RealmId:\"4\",\n Tasks: nil,\n Body:\"Project 22\",\n Due:\"\",\n },\n }, \n },\n {\n Id: \"40\",\n Name: \"Collection 40\",\n Tasks: nil, \n Projects: nil, \n },\n }\n \n\n // Manually marshal the known collections to create the expected outcome.\n collectionsObject := CollectionsObject{Collections: knownCollections}\n expected, err := collectionsObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known collections: %v\", err)\n }\n\n // Execute GetAllCollections() to get the actual outcome.\n actual, err := GetAllCollections()\n if err != nil {\n t.Fatalf(\"GetAllCollections() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual collections JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n\n\n\n\n" + }, + { + "name": "contexts.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\ntype Context struct {\n\tId string `json:\"contextId\"`\n\tName string `json:\"contextName\"`\n}\n\ntype ZContextManager struct {\n\tContexts *avl.Tree\n}\n\nfunc NewZContextManager() *ZContextManager {\n\treturn &ZContextManager{\n\t\tContexts: avl.NewTree(),\n\t}\n}\n\n// Actions\n\nfunc (zcm *ZContextManager) AddContext(c Context) error {\n\tif zcm.Contexts.Size() != 0 {\n\t\t_, exist := zcm.Contexts.Get(c.Id)\n\t\tif exist {\n\t\t\treturn ErrContextIdAlreadyExists\n\t\t}\n\t}\n\tzcm.Contexts.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) EditContext(c Context) error {\n\tif zcm.Contexts.Size() != 0 {\n\t\t_, exist := zcm.Contexts.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrContextIdNotFound\n\t\t}\n\t}\n\tzcm.Contexts.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) RemoveContext(c Context) error {\n\tif zcm.Contexts.Size() != 0 {\n\t\tcontext, exist := zcm.Contexts.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrContextIdNotFound\n\t\t}\n\t\t_, removed := zcm.Contexts.Remove(context.(Context).Id)\n\t\tif !removed {\n\t\t\treturn ErrContextNotRemoved\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) AddContextToTask(ztm *ZTaskManager, c Context, t Task) error {\n\ttaskInterface, exist := ztm.Tasks.Get(t.Id)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\t_, cexist := zcm.Contexts.Get(c.Id)\n\tif !cexist {\n\t\treturn ErrContextIdNotFound\n\t}\n\n\tif t.RealmId == \"2\" {\n\t\ttask := taskInterface.(Task)\n\t\ttask.ContextId = c.Id\n\t\tztm.Tasks.Set(t.Id, task)\n\t} else {\n\t\treturn ErrTaskNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) AddContextToProject(zpm *ZProjectManager, c Context, p Project) error {\n\tprojectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\t_, cexist := zcm.Contexts.Get(c.Id)\n\tif !cexist {\n\t\treturn ErrContextIdNotFound\n\t}\n\n\tif p.RealmId == \"2\" {\n\t\tproject := projectInterface.(Project)\n\t\tproject.ContextId = c.Id\n\t\tzpm.Projects.Set(p.Id, project)\n\t} else {\n\t\treturn ErrProjectNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) AddContextToProjectTask(zpm *ZProjectManager, c Context, p Project, projectTaskId string) error {\n\t\n\t_, cexist := zcm.Contexts.Get(c.Id)\n\tif !cexist {\n\t\treturn ErrContextIdNotFound\n\t}\n\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n\tif existingProject.RealmId != \"2\" {\n\t\treturn ErrProjectNotEditable\n\t}\n\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(p.Id)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n existingProject.Tasks = tasks\n\n var index int = -1\n for i, task := range existingProject.Tasks {\n if task.Id == projectTaskId {\n index = i\n break\n }\n }\n\n if index != -1 {\n existingProject.Tasks[index].ContextId = c.Id\n } else {\n return ErrTaskByIdNotFound\n }\n\n zpm.ProjectTasks.Set(p.Id, existingProject.Tasks)\n return nil\n}\n\n// getters\n\nfunc (zcm *ZContextManager) GetContextById(contextId string) (Context, error) {\n\tif zcm.Contexts.Size() != 0 {\n\t\tcInterface, exist := zcm.Contexts.Get(contextId)\n\t\tif exist {\n\t\t\treturn cInterface.(Context), nil\n\t\t}\n\t\treturn Context{}, ErrContextIdNotFound\n\t}\n\treturn Context{}, ErrContextIdNotFound\n}\n\nfunc (zcm *ZContextManager) GetAllContexts() (string) {\n\tvar allContexts []Context\n\n\t// Iterate over the Contexts AVL tree to collect all Context objects.\n\tzcm.Contexts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif context, ok := value.(Context); ok {\n\t\t\tallContexts = append(allContexts, context)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ContextsObject with all collected contexts.\n\tcontextsObject := &ContextsObject{\n\t\tContexts: allContexts,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the contexts into JSON.\n\tmarshalledContexts, merr := contextsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t}\n\treturn string(marshalledContexts)\n}\n\n" + }, + { + "name": "contexts_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n/*\nfunc Test_AddContext(t *testing.T) {\n \n context := Context{Id: \"1\", Name: \"Work\"}\n\n // Test adding a context successfully.\n err := context.AddContext()\n if err != nil {\n t.Errorf(\"Failed to add context: %v\", err)\n }\n\n // Test adding a duplicate task.\n cerr := context.AddContext()\n if cerr != ErrContextIdAlreadyExists {\n t.Errorf(\"Expected ErrContextIdAlreadyExists, got %v\", cerr)\n }\n}\n\nfunc Test_EditContext(t *testing.T) {\n \n context := Context{Id: \"2\", Name: \"Home\"}\n\n // Test adding a context successfully.\n err := context.AddContext()\n if err != nil {\n t.Errorf(\"Failed to add context: %v\", err)\n }\n\n // Test editing the context\n editedContext := Context{Id: \"2\", Name: \"Shopping\"}\n cerr := editedContext.EditContext()\n if cerr != nil {\n t.Errorf(\"Failed to edit the context\")\n }\n\n retrievedContext, _ := GetContextById(editedContext.Id)\n if retrievedContext.Name != \"Shopping\" {\n t.Errorf(\"Context was not edited\")\n }\n}\n\nfunc Test_RemoveContext(t *testing.T) {\n \n context := Context{Id: \"4\", Name: \"Gym\",}\n\n // Test adding a context successfully.\n err := context.AddContext()\n if err != nil {\n t.Errorf(\"Failed to add context: %v\", err)\n }\n\n retrievedContext, rerr := GetContextById(context.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added context\")\n }\n // Test removing a context\n cerr := retrievedContext.RemoveContext()\n if cerr != ErrContextNotRemoved {\n t.Errorf(\"Expected ErrContextNotRemoved, got %v\", cerr)\n }\n}\n\nfunc Test_AddContextToTask(t *testing.T) {\n\n task := Task{Id: \"10\", Body: \"First content\", RealmId: \"2\", ContextId: \"1\",}\n\n // Test adding a task successfully.\n err := task.AddTask()\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n taskInDecide, exist := Tasks.Get(\"10\")\n\tif !exist {\n\t\tt.Errorf(\"Task with id 10 not found\")\n\t}\n\t// check if context exists\n\tcontextToAdd, cexist := Contexts.Get(\"2\")\n\tif !cexist {\n\t\tt.Errorf(\"Context with id 2 not found\")\n\t}\n\n derr := contextToAdd.(Context).AddContextToTask(taskInDecide.(Task))\n if derr != nil {\n t.Errorf(\"Could not add context to a task in Decide, err %v\", derr)\n }\n}\n\nfunc Test_AddContextToProject(t *testing.T) {\n\n project := Project{Id: \"10\", Body: \"Project 10\", RealmId: \"2\", ContextId: \"1\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n projectInDecide, exist := Projects.Get(\"10\")\n\tif !exist {\n\t\tt.Errorf(\"Project with id 10 not found\")\n\t}\n\t// check if context exists\n\tcontextToAdd, cexist := Contexts.Get(\"2\")\n\tif !cexist {\n\t\tt.Errorf(\"Context with id 2 not found\")\n\t}\n\n derr := contextToAdd.(Context).AddContextToProject(projectInDecide.(Project))\n if derr != nil {\n t.Errorf(\"Could not add context to a project in Decide, err %v\", derr)\n }\n}\n\nfunc Test_GetAllContexts(t *testing.T) {\n \n // mocking the contexts based on previous tests\n // TODO: add isolation?\n knownContexts := []Context{\n {Id: \"1\", Name: \"Work\",},\n {Id: \"2\", Name: \"Shopping\",},\n {Id: \"4\", Name: \"Gym\",},\n }\n\n // Manually marshal the known contexts to create the expected outcome.\n contextsObject := ContextsObject{Contexts: knownContexts}\n expected, err := contextsObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known contexts: %v\", err)\n }\n\n // Execute GetAllContexts() to get the actual outcome.\n actual, err := GetAllContexts()\n if err != nil {\n t.Fatalf(\"GetAllContexts() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual contexts JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n\n" + }, + { + "name": "core.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// holding the path of an object since creation\n// each time we move an object from one realm to another, we add to its path\ntype ObjectPath struct {\n\tObjectType string `json:\"objectType\"` // Task, Project\n\tId string `json:\"id\"` // this is the Id of the object moved, Task, Project\n\tRealmId string `json:\"realmId\"`\n}\n\ntype ZObjectPathManager struct {\n\tPaths avl.Tree\n\tPathId int\n}\n\nfunc NewZObjectPathManager() *ZObjectPathManager {\n\treturn &ZObjectPathManager{\n\t\tPaths: *avl.NewTree(),\n\t\tPathId: 1,\n\t}\n}\n\nfunc (zopm *ZObjectPathManager) AddPath(o ObjectPath) error {\n\tzopm.PathId++\n\tupdated := zopm.Paths.Set(strconv.Itoa(zopm.PathId), o)\n\tif !updated {\n\t\treturn ErrObjectPathNotUpdated\n\t}\n\treturn nil\n}\n\nfunc (zopm *ZObjectPathManager) GetObjectJourney(objectType string, objectId string) (string, error) {\n\tvar objectPaths []ObjectPath\n\n\t// Iterate over the Paths AVL tree to collect all ObjectPath objects.\n\tzopm.Paths.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif objectPath, ok := value.(ObjectPath); ok {\n\t\t\tif objectPath.ObjectType == objectType && objectPath.Id == objectId {\n\t\t\t\tobjectPaths = append(objectPaths, objectPath)\n\t\t\t}\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create an ObjectJourney with all collected paths.\n\tobjectJourney := &ObjectJourney{\n\t\tObjectPaths: objectPaths,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the journey into JSON.\n\tmarshalledJourney, merr := objectJourney.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\", merr\n\t}\n\treturn string(marshalledJourney), nil\n}\n\n\n// GetZenStatus\n/* todo: leave it to the client\nfunc () GetZenStatus() (zenStatus string, err error) {\n\t// implementation\n}\n*/\n" + }, + { + "name": "errors.gno", + "body": "package zentasktic\n\nimport \"errors\"\n\nvar (\n\tErrTaskNotEditable \t= errors.New(\"Task is not editable\")\n\tErrProjectNotEditable = errors.New(\"Project is not editable\")\n\tErrProjectIdNotFound\t\t\t= errors.New(\"Project id not found\")\n\tErrTaskIdNotFound\t\t\t\t= errors.New(\"Task id not found\")\n\tErrTaskFailedToAssert\t\t\t= errors.New(\"Failed to assert Task type\")\n\tErrProjectFailedToAssert\t\t= errors.New(\"Failed to assert Project type\")\n\tErrProjectTasksNotFound\t\t\t= errors.New(\"Could not get tasks for project\")\n\tErrCollectionsProjectsNotFound\t= errors.New(\"Could not get projects for this collection\")\n\tErrCollectionsTasksNotFound\t\t= errors.New(\"Could not get tasks for this collection\")\n\tErrTaskIdAlreadyExists\t\t\t= errors.New(\"A task with the provided id already exists\")\n\tErrCollectionIdAlreadyExists\t= errors.New(\"A collection with the provided id already exists\")\n\tErrProjectIdAlreadyExists\t\t= errors.New(\"A project with the provided id already exists\")\n\tErrTaskByIdNotFound\t\t\t\t= errors.New(\"Can't get task by id\")\n\tErrProjectByIdNotFound\t\t\t= errors.New(\"Can't get project by id\")\n\tErrCollectionByIdNotFound\t\t= errors.New(\"Can't get collection by id\")\n\tErrTaskNotRemovable\t\t\t\t= errors.New(\"Cannot remove a task directly from this realm\")\n\tErrProjectNotRemovable\t\t\t= errors.New(\"Cannot remove a project directly from this realm\")\n\tErrProjectTasksNotRemoved\t\t= errors.New(\"Project tasks were not removed\")\n\tErrTaskNotRemoved\t\t\t\t= errors.New(\"Task was not removed\")\n\tErrTaskNotInAssessRealm\t\t\t= errors.New(\"Task is not in Assess, cannot edit Body\")\n\tErrProjectNotInAssessRealm\t\t= errors.New(\"Project is not in Assess, cannot edit Body\")\n\tErrContextIdAlreadyExists\t\t= errors.New(\"A context with the provided id already exists\")\n\tErrContextIdNotFound\t\t\t= errors.New(\"Context id not found\")\n\tErrCollectionIdNotFound\t\t\t= errors.New(\"Collection id not found\")\n\tErrContextNotRemoved\t\t\t= errors.New(\"Context was not removed\")\n\tErrProjectNotRemoved\t\t\t= errors.New(\"Project was not removed\")\n\tErrCollectionNotRemoved\t\t\t= errors.New(\"Collection was not removed\")\n\tErrObjectPathNotUpdated\t\t\t= errors.New(\"Object path wasn't updated\")\n\tErrInvalidateDateFormat\t\t\t= errors.New(\"Invalida date format\")\n\tErrInvalidDateFilterType\t\t= errors.New(\"Invalid date filter type\")\n\tErrRealmIdAlreadyExists\t\t\t= errors.New(\"A realm with the same id already exists\")\n\tErrRealmIdNotAllowed\t\t\t= errors.New(\"This is a reserved realm id\")\n\tErrRealmIdNotFound\t\t\t\t= errors.New(\"Realm id not found\")\n\tErrRealmNotRemoved\t\t\t\t= errors.New(\"Realm was not removed\")\n)" + }, + { + "name": "marshals.gno", + "body": "package zentasktic\n\nimport (\n\t\"bytes\"\n)\n\n\ntype ContextsObject struct {\n\tContexts\t[]Context\n}\n\ntype TasksObject struct {\n\tTasks\t[]Task\n}\n\ntype ProjectsObject struct {\n\tProjects\t[]Project\n}\n\ntype CollectionsObject struct {\n\tCollections\t[]Collection\n}\n\ntype RealmsObject struct {\n\tRealms\t[]Realm\n}\n\ntype ObjectJourney struct {\n\tObjectPaths []ObjectPath\n}\n\nfunc (c Context) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"contextId\":\"`)\n\tb.WriteString(c.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"contextName\":\"`)\n\tb.WriteString(c.Name)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (cs ContextsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"contexts\":[`)\n\t\n\tfor i, context := range cs.Contexts {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcontextJSON, cerr := context.MarshalJSON()\n\t\tif cerr == nil {\n\t\t\tb.WriteString(string(contextJSON))\n\t\t}\n\t}\n\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (t Task) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"taskId\":\"`)\n\tb.WriteString(t.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskProjectId\":\"`)\n\tb.WriteString(t.ProjectId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskContextId\":\"`)\n\tb.WriteString(t.ContextId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskRealmId\":\"`)\n\tb.WriteString(t.RealmId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskBody\":\"`)\n\tb.WriteString(t.Body)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskDue\":\"`)\n\tb.WriteString(t.Due)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskAlert\":\"`)\n\tb.WriteString(t.Alert)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (ts TasksObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"tasks\":[`)\n\tfor i, task := range ts.Tasks {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\ttaskJSON, cerr := task.MarshalJSON()\n\t\tif cerr == nil {\n\t\t\tb.WriteString(string(taskJSON))\n\t\t}\n\t}\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (p Project) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"projectId\":\"`)\n\tb.WriteString(p.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectContextId\":\"`)\n\tb.WriteString(p.ContextId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectRealmId\":\"`)\n\tb.WriteString(p.RealmId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectTasks\":[`)\n\n\tif len(p.Tasks) != 0 {\n\t\tfor i, projectTask := range p.Tasks {\n\t\t\tif i > 0 {\n\t\t\t\tb.WriteString(`,`)\n\t\t\t}\n\t\t\tprojectTaskJSON, perr := projectTask.MarshalJSON()\n\t\t\tif perr == nil {\n\t\t\t\tb.WriteString(string(projectTaskJSON))\n\t\t\t}\n\t\t}\n\t}\n\n\tb.WriteString(`],`)\n\n\tb.WriteString(`\"projectBody\":\"`)\n\tb.WriteString(p.Body)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectDue\":\"`)\n\tb.WriteString(p.Due)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (ps ProjectsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"projects\":[`)\n\tfor i, project := range ps.Projects {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tprojectJSON, perr := project.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(projectJSON))\n\t\t}\n\t}\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (c Collection) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"collectionId\":\"`)\n\tb.WriteString(c.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"collectionRealmId\":\"`)\n\tb.WriteString(c.RealmId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"collectionName\":\"`)\n\tb.WriteString(c.Name)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"collectionTasks\":[`)\n\tfor i, collectionTask := range c.Tasks {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcollectionTaskJSON, perr := collectionTask.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(collectionTaskJSON))\n\t\t}\n\t}\n\tb.WriteString(`],`)\n\n\tb.WriteString(`\"collectionProjects\":[`)\n\tfor i, collectionProject := range c.Projects {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcollectionProjectJSON, perr := collectionProject.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(collectionProjectJSON))\n\t\t}\n\t}\n\tb.WriteString(`],`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (co CollectionsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"collections\":[`)\n\tfor i, collection := range co.Collections {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcollectionJSON, perr := collection.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(collectionJSON))\n\t\t}\n\t}\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (r Realm) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"realmId\":\"`)\n\tb.WriteString(r.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"realmName\":\"`)\n\tb.WriteString(r.Name)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (rs RealmsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"realms\":[`)\n\t\n\tfor i, realm := range rs.Realms {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\trealmJSON, rerr := realm.MarshalJSON()\n\t\tif rerr == nil {\n\t\t\tb.WriteString(string(realmJSON))\n\t\t}\n\t}\n\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (op ObjectPath) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"objectType\":\"`)\n\tb.WriteString(op.ObjectType)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"id\":\"`)\n\tb.WriteString(op.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"realmId\":\"`)\n\tb.WriteString(op.RealmId)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (oj ObjectJourney) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"objectJourney\":[`)\n\t\n\tfor i, objectPath := range oj.ObjectPaths {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tobjectPathJSON, oerr := objectPath.MarshalJSON()\n\t\tif oerr == nil {\n\t\t\tb.WriteString(string(objectPathJSON))\n\t\t}\n\t}\n\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n" + }, + { + "name": "projects.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n\ntype Project struct {\n\tId \t\t\tstring `json:\"projectId\"`\n\tContextId\tstring `json:\"projectContextId\"`\n\tRealmId \tstring `json:\"projectRealmId\"`\n\tTasks\t\t[]Task `json:\"projectTasks\"`\n\tBody \t\tstring `json:\"projectBody\"`\n\tDue\t\t\tstring `json:\"projectDue\"`\n}\n\ntype ZProjectManager struct {\n\tProjects *avl.Tree // projectId -> Project\n\tProjectTasks *avl.Tree // projectId -> []Task\n}\n\n\nfunc NewZProjectManager() *ZProjectManager {\n\treturn &ZProjectManager{\n\t\tProjects: avl.NewTree(),\n\t\tProjectTasks: avl.NewTree(),\n\t}\n}\n\n// actions\n\nfunc (zpm *ZProjectManager) AddProject(p Project) (err error) {\n\t// implementation\n\n\tif zpm.Projects.Size() != 0 {\n\t\t_, exist := zpm.Projects.Get(p.Id)\n\t\tif exist {\n\t\t\treturn ErrProjectIdAlreadyExists\n\t\t}\n\t}\n\tzpm.Projects.Set(p.Id, p)\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) RemoveProject(p Project) (err error) {\n\t// implementation, remove from ProjectTasks too\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\t // project is removable only in Asses (RealmId 1) or via a Collection (RealmId 4)\n\tif existingProject.RealmId != \"1\" && existingProject.RealmId != \"4\" {\n\t\treturn ErrProjectNotRemovable\n\t}\n\n\t_, removed := zpm.Projects.Remove(existingProject.Id)\n\tif !removed {\n\t\treturn ErrProjectNotRemoved\n\t}\n\n\t// manage project tasks, if any\n\n\tif zpm.ProjectTasks.Size() != 0 {\n\t\t_, exist := zpm.ProjectTasks.Get(existingProject.Id)\n\t\tif !exist {\n\t\t\t// if there's no record in ProjectTasks, we don't have to remove anything\n\t\t\treturn nil\n\t\t} else {\n\t\t\t_, removed := zpm.ProjectTasks.Remove(existingProject.Id)\n\t\t\tif !removed {\n\t\t\t\treturn ErrProjectTasksNotRemoved\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) EditProject(p Project) (err error) {\n\t// implementation, get project by Id and replace the object\n\t// this is for the project body and realm, project tasks are managed in the Tasks object\n\texistingProject := Project{}\n\tif zpm.Projects.Size() != 0 {\n\t\t_, exist := zpm.Projects.Get(p.Id)\n\t\tif !exist {\n\t\t\treturn ErrProjectIdNotFound\n\t\t}\n\t}\n\t\n\t// project Body is editable only when project is in Assess, RealmId = \"1\"\n\tif p.RealmId != \"1\" {\n\t\tif p.Body != existingProject.Body {\n\t\t\treturn ErrProjectNotInAssessRealm\n\t\t}\n\t}\n\n\tzpm.Projects.Set(p.Id, p)\n\treturn nil\n}\n\n// helper function, we can achieve the same with EditProject() above\n/*func (zpm *ZProjectManager) MoveProjectToRealm(projectId string, realmId string) (err error) {\n\t// implementation\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\texistingProject.RealmId = realmId\n\tzpm.Projects.Set(projectId, existingProject)\n\treturn nil\n}*/\n\nfunc (zpm *ZProjectManager) MoveProjectToRealm(projectId string, realmId string) error {\n\t// Get the existing project from the Projects map\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\t// Set the project's RealmId to the new RealmId\n\texistingProject.RealmId = realmId\n\n\t// Get the existing project tasks from the ProjectTasks map\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n\tif !texist {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\ttasks, ok := existingProjectTasksInterface.([]Task)\n\tif !ok {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\n\t// Iterate through the project's tasks and set their RealmId to the new RealmId\n\tfor i := range tasks {\n\t\ttasks[i].RealmId = realmId\n\t}\n\n\t// Set the updated tasks back into the ProjectTasks map\n\tzpm.ProjectTasks.Set(projectId, tasks)\n\n\t// Set the updated project back into the Projects map\n\tzpm.Projects.Set(projectId, existingProject)\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) MarkProjectTaskAsDone(projectId string, projectTaskId string) error {\n // Get the existing project from the Projects map\n existingProjectInterface, exist := zpm.Projects.Get(projectId)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n // Get the existing project tasks from the ProjectTasks map\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n\n // Iterate through the project's tasks to find the task to be updated\n var taskFound bool\n for i, task := range tasks {\n if task.Id == projectTaskId {\n tasks[i].RealmId = \"4\" // Change the RealmId to \"4\"\n taskFound = true\n break\n }\n }\n\n if !taskFound {\n return ErrTaskByIdNotFound\n }\n\n // Set the updated tasks back into the ProjectTasks map\n zpm.ProjectTasks.Set(existingProject.Id, tasks)\n\n return nil\n}\n\n\nfunc (zpm *ZProjectManager) GetProjectTasks(p Project) (tasks []Task, err error) {\n\t// implementation, query ProjectTasks and return the []Tasks object\n\tvar existingProjectTasks []Task\n\n\tif zpm.ProjectTasks.Size() != 0 {\n\t\tprojectTasksInterface, exist := zpm.ProjectTasks.Get(p.Id)\n\t\tif !exist {\n\t\t\treturn nil, ErrProjectTasksNotFound\n\t\t}\n\t\texistingProjectTasks = projectTasksInterface.([]Task)\n\t\treturn existingProjectTasks, nil\n\t}\n\treturn nil, nil\n}\n\nfunc (zpm *ZProjectManager) SetProjectDueDate(projectId string, dueDate string) (err error) {\n\tprojectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\tproject := projectInterface.(Project)\n\n\t// check to see if project is in RealmId = 2 (Decide)\n\tif project.RealmId == \"2\" {\n\t\tproject.Due = dueDate\n\t\tzpm.Projects.Set(project.Id, project)\n\t} else {\n\t\treturn ErrProjectNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) SetProjectTaskDueDate(projectId string, projectTaskId string, dueDate string) (err error){\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n\tif existingProject.RealmId != \"2\" {\n\t\treturn ErrProjectNotEditable\n\t}\n\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n existingProject.Tasks = tasks\n\n var index int = -1\n for i, task := range existingProject.Tasks {\n if task.Id == projectTaskId {\n index = i\n break\n }\n }\n\n if index != -1 {\n existingProject.Tasks[index].Due = dueDate\n } else {\n return ErrTaskByIdNotFound\n }\n\n zpm.ProjectTasks.Set(projectId, existingProject.Tasks)\n return nil\n}\n\n// getters\n\nfunc (zpm *ZProjectManager) GetProjectById(projectId string) (Project, error) {\n\tif zpm.Projects.Size() != 0 {\n\t\tpInterface, exist := zpm.Projects.Get(projectId)\n\t\tif exist {\n\t\t\treturn pInterface.(Project), nil\n\t\t}\n\t}\n\treturn Project{}, ErrProjectIdNotFound\n}\n\nfunc (zpm *ZProjectManager) GetAllProjects() (projects string) {\n\t// implementation\n\tvar allProjects []Project\n\t\n\t// Iterate over the Projects AVL tree to collect all Project objects.\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif project, ok := value.(Project); ok {\n\t\t\t// get project tasks, if any\n\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\tif projectTasks != nil {\n\t\t\t\tproject.Tasks = projectTasks\n\t\t\t}\n\t\t\tallProjects = append(allProjects, project)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: allProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n}\n\nfunc (zpm *ZProjectManager) GetProjectsByRealm(realmId string) (projects string) {\n\t// implementation\n\tvar realmProjects []Project\n\t\n\t// Iterate over the Projects AVL tree to collect all Project objects.\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif project, ok := value.(Project); ok {\n\t\t\tif project.RealmId == realmId {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\trealmProjects = append(realmProjects, project)\n\t\t\t}\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: realmProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n}\n\nfunc (zpm *ZProjectManager) GetProjectsByContextAndRealm(contextId string, realmId string) (projects string) {\n\t// implementation\n\tvar contextProjects []Project\n\t\n\t// Iterate over the Projects AVL tree to collect all Project objects.\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif project, ok := value.(Project); ok {\n\t\t\tif project.ContextId == contextId && project.RealmId == realmId {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tcontextProjects = append(contextProjects, project)\n\t\t\t}\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: contextProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n}\n\nfunc (zpm *ZProjectManager) GetProjectsByDate(projectDate string, filterType string) (projects string) {\n\t// implementation\n\tparsedDate, err:= time.Parse(\"2006-01-02\", projectDate)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tvar filteredProjects []Project\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tproject, ok := value.(Project)\n\t\tif !ok {\n\t\t\treturn false // Skip this iteration and continue.\n\t\t}\n\n\t\tstoredDate, serr := time.Parse(\"2006-01-02\", project.Due)\n\t\tif serr != nil {\n\t\t\t// Skip projects with invalid dates.\n\t\t\treturn false\n\t\t}\n\n\t\tswitch filterType {\n\t\tcase \"specific\":\n\t\t\tif storedDate.Format(\"2006-01-02\") == parsedDate.Format(\"2006-01-02\") {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tfilteredProjects = append(filteredProjects, project)\n\t\t\t}\n\t\tcase \"before\":\n\t\t\tif storedDate.Before(parsedDate) {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tfilteredProjects = append(filteredProjects, project)\n\t\t\t}\n\t\tcase \"after\":\n\t\t\tif storedDate.After(parsedDate) {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tfilteredProjects = append(filteredProjects, project)\n\t\t\t}\n\t\t}\n\n\t\treturn false // Continue iteration.\n\t})\n\n\tif len(filteredProjects) == 0 {\n\t\treturn \"\"\n\t}\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: filteredProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n\n}\n" + }, + { + "name": "projects_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n/*\nfunc Test_AddProject(t *testing.T) {\n \n project := Project{Id: \"1\", RealmId: \"1\", Body: \"First project\", ContextId: \"1\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n // Test adding a duplicate project.\n cerr := project.AddProject()\n if cerr != ErrProjectIdAlreadyExists {\n t.Errorf(\"Expected ErrProjectIdAlreadyExists, got %v\", cerr)\n }\n}\n\n\nfunc Test_RemoveProject(t *testing.T) {\n \n project := Project{Id: \"20\", Body: \"Removable project\", RealmId: \"1\", ContextId: \"2\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n retrievedProject, rerr := GetProjectById(project.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added project\")\n }\n\n // Test removing a project\n terr := retrievedProject.RemoveProject()\n if terr != ErrProjectNotRemoved {\n t.Errorf(\"Expected ErrProjectNotRemoved, got %v\", terr)\n }\n}\n\n\nfunc Test_EditProject(t *testing.T) {\n \n project := Project{Id: \"2\", Body: \"Second project content\", RealmId: \"1\", ContextId: \"2\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n // Test editing the project\n editedProject := Project{Id: project.Id, Body: \"Edited project content\", RealmId: project.RealmId, ContextId: \"2\",}\n cerr := editedProject.EditProject()\n if cerr != nil {\n t.Errorf(\"Failed to edit the project\")\n }\n\n retrievedProject, _ := GetProjectById(editedProject.Id)\n if retrievedProject.Body != \"Edited project content\" {\n t.Errorf(\"Project was not edited\")\n }\n}\n\n\nfunc Test_MoveProjectToRealm(t *testing.T) {\n \n project := Project{Id: \"3\", Body: \"Project id 3 content\", RealmId: \"1\", ContextId: \"1\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n // Test moving the project to another realm\n \n cerr := project.MoveProjectToRealm(\"2\")\n if cerr != nil {\n t.Errorf(\"Failed to move project to another realm\")\n }\n\n retrievedProject, _ := GetProjectById(project.Id)\n if retrievedProject.RealmId != \"2\" {\n t.Errorf(\"Project was moved to the wrong realm\")\n }\n}\n\nfunc Test_SetProjectDueDate(t *testing.T) {\n\tprojectRealmIdOne, _ := GetProjectById(\"1\")\n projectRealmIdTwo, _ := GetProjectById(\"10\") \n\t// Define test cases\n\ttests := []struct {\n\t\tname string\n\t\tproject Project\n\t\tdueDate string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"Project does not exist\",\n\t\t\tproject: Project{Id: \"nonexistent\", RealmId: \"2\"},\n\t\t\twantErr: ErrProjectIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Project not editable due to wrong realm\",\n\t\t\tproject: projectRealmIdOne,\n\t\t\twantErr: ErrProjectNotEditable,\n\t\t},\n\t\t{\n\t\t\tname: \"Successfully set alert\",\n\t\t\tproject: projectRealmIdTwo,\n\t\t\tdueDate: \"2024-01-01\",\n\t\t\twantErr: nil,\n\t\t},\n\t}\n\n\t// Execute test cases\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := tc.project.SetProjectDueDate(tc.dueDate)\n\n\t\t\t// Validate\n\t\t\tif err != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error %v, got %v\", tc.wantErr, err)\n\t\t\t}\n\n\t\t\t// Additional check for the success case to ensure the due date was actually set\n\t\t\tif err == nil {\n\t\t\t\t// Fetch the task again to check if the due date was set correctly\n\t\t\t\tupdatedProject, exist := Projects.Get(tc.project.Id)\n\t\t\t\tif !exist {\n\t\t\t\t\tt.Fatalf(\"Project %v was not found after setting the due date\", tc.project.Id)\n\t\t\t\t}\n\t\t\t\tif updatedProject.(Project).Due != tc.dueDate {\n\t\t\t\t\tt.Errorf(\"Expected due date to be %v, got %v\", tc.dueDate, updatedProject.(Project).Due)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// getters\n\nfunc Test_GetAllProjects(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n knownProjects := []Project{\n {Id: \"1\", Body: \"First project\", RealmId: \"1\", ContextId: \"1\",},\n {Id: \"10\", Body: \"Project 10\", RealmId: \"2\", ContextId: \"2\", Due: \"2024-01-01\"},\n\t\t{Id: \"2\", Body: \"Edited project content\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable project\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"21\", Body: \"Project 21\", RealmId: \"1\",},\n {Id: \"22\", Body: \"Project 22\", RealmId: \"1\",},\n\t\t{Id: \"3\", Body: \"Project id 3 content\", RealmId: \"2\", ContextId: \"1\",},\n }\n\n // Manually marshal the known projects to create the expected outcome.\n projectsObject := ProjectsObject{Projects: knownProjects}\n expected, err := projectsObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known projects: %v\", err)\n }\n\n // Execute GetAllProjects() to get the actual outcome.\n actual, err := GetAllProjects()\n if err != nil {\n t.Fatalf(\"GetAllProjects() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual project JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetProjectsByDate(t *testing.T) {\n\t\n\ttests := []struct {\n\t\tname string\n\t\tprojectDate string\n\t\tfilterType string\n\t\twant string\n\t\twantErr bool\n\t}{\n\t\t{\"SpecificDate\", \"2024-01-01\", \"specific\", `{\"projects\":[{\"projectId\":\"10\",\"projectContextId\":\"2\",\"projectRealmId\":\"2\",\"projectTasks\":[],\"projectBody\":\"Project 10\",\"projectDue\":\"2024-01-01\"}]}`, false},\n\t\t{\"BeforeDate\", \"2022-04-05\", \"before\", \"\", false},\n\t\t{\"AfterDate\", \"2025-04-05\", \"after\", \"\", false},\n\t\t{\"NoMatch\", \"2002-04-07\", \"specific\", \"\", false},\n\t\t{\"InvalidDateFormat\", \"April 5, 2023\", \"specific\", \"\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := GetProjectsByDate(tt.projectDate, tt.filterType)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetProjectsByDate() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && got != tt.want {\n\t\t\t\tt.Errorf(\"GetProjectsByDate() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_GetProjectTasks(t *testing.T){\n \n task := Task{Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",}\n\n project, perr := GetProjectById(\"1\")\n if perr != nil {\n t.Errorf(\"GetProjectById() failed, %v\", perr)\n }\n\n // test attaching to an existing project\n atterr := task.AttachTaskToProject(project)\n if atterr != nil {\n t.Errorf(\"AttachTaskToProject() failed, %v\", atterr)\n }\n\n projectTasks, pterr := project.GetProjectTasks()\n if len(projectTasks) == 0 {\n t.Errorf(\"GetProjectTasks() failed, %v\", pterr)\n }\n\n // test detaching from an existing project\n dtterr := task.DetachTaskFromProject(project)\n if dtterr != nil {\n t.Errorf(\"DetachTaskFromProject() failed, %v\", dtterr)\n }\n\n projectWithNoTasks, pterr := project.GetProjectTasks()\n if len(projectWithNoTasks) != 0 {\n t.Errorf(\"GetProjectTasks() after detach failed, %v\", pterr)\n }\n}\n\nfunc Test_GetProjectById(t *testing.T){\n // test getting a non-existing project\n nonProject, err := GetProjectById(\"0\")\n if err != ErrProjectByIdNotFound {\n t.Fatalf(\"Expected ErrProjectByIdNotFound, got: %v\", err)\n }\n\n // test getting the correct task by id\n correctProject, err := GetProjectById(\"1\")\n if err != nil {\n t.Fatalf(\"Failed to get project by id, error: %v\", err)\n }\n\n if correctProject.Body != \"First project\" {\n t.Fatalf(\"Got the wrong project, with body: %v\", correctProject.Body)\n }\n}\n\nfunc Test_GetProjectsByRealm(t *testing.T) {\n \n // mocking the projects based on previous tests\n // TODO: add isolation?\n projectsInAssessRealm := []Project{\n {Id: \"1\", Body: \"First project\", RealmId: \"1\", ContextId: \"1\",},\n\t\t{Id: \"2\", Body: \"Edited project content\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable project\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"21\", Body: \"Project 21\", RealmId: \"1\",},\n {Id: \"22\", Body: \"Project 22\", RealmId: \"1\",},\n }\n\n // Manually marshal the known projects to create the expected outcome.\n projectsObjectAssess := ProjectsObject{Projects: projectsInAssessRealm}\n expected, err := projectsObjectAssess.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal projects in Assess: %v\", err)\n }\n\n actual, err := GetProjectsByRealm(\"1\")\n if err != nil {\n t.Fatalf(\"GetProjectByRealm('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual projects JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetProjectsByContext(t *testing.T) {\n \n // mocking the projects based on previous tests\n // TODO: add isolation?\n projectsInContextOne := []Project{\n {Id: \"1\", Body: \"First project\", RealmId: \"1\", ContextId: \"1\",},\n\t\t{Id: \"3\", Body: \"Project id 3 content\", RealmId: \"2\", ContextId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n projectsObjectForContexts := ProjectsObject{Projects: projectsInContextOne}\n expected, err := projectsObjectForContexts.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal projects for ContextId 1: %v\", err)\n }\n\n actual, err := GetProjectsByContext(\"1\")\n if err != nil {\n t.Fatalf(\"GetProjectsByContext('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual project JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n\n" + }, + { + "name": "realms.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\n// structs\n\ntype Realm struct {\n\tId \t\t\tstring `json:\"realmId\"`\n\tName \t\tstring `json:\"realmName\"`\n}\n\ntype ZRealmManager struct {\n\tRealms *avl.Tree\n}\n\nfunc NewZRealmManager() *ZRealmManager {\n\tzrm := &ZRealmManager{\n\t\tRealms: avl.NewTree(),\n\t}\n\tzrm.initializeHardcodedRealms()\n\treturn zrm\n}\n\n\nfunc (zrm *ZRealmManager) initializeHardcodedRealms() {\n\thardcodedRealms := []Realm{\n\t\t{Id: \"1\", Name: \"Assess\"},\n\t\t{Id: \"2\", Name: \"Decide\"},\n\t\t{Id: \"3\", Name: \"Do\"},\n\t\t{Id: \"4\", Name: \"Collections\"},\n\t}\n\n\tfor _, realm := range hardcodedRealms {\n\t\tzrm.Realms.Set(realm.Id, realm)\n\t}\n}\n\n\nfunc (zrm *ZRealmManager) AddRealm(r Realm) (err error){\n\t// implementation\n\tif zrm.Realms.Size() != 0 {\n\t\t_, exist := zrm.Realms.Get(r.Id)\n\t\tif exist {\n\t\t\treturn ErrRealmIdAlreadyExists\n\t\t}\n\t}\n\t// check for hardcoded values\n\tif r.Id == \"1\" || r.Id == \"2\" || r.Id == \"3\" || r.Id == \"4\" {\n\t\treturn ErrRealmIdNotAllowed\n\t}\n\tzrm.Realms.Set(r.Id, r)\n\treturn nil\n\t\n}\n\nfunc (zrm *ZRealmManager) RemoveRealm(r Realm) (err error){\n\t// implementation\n\tif zrm.Realms.Size() != 0 {\n\t\t_, exist := zrm.Realms.Get(r.Id)\n\t\tif !exist {\n\t\t\treturn ErrRealmIdNotFound\n\t\t} else {\n\t\t\t// check for hardcoded values, not removable\n\t\t\tif r.Id == \"1\" || r.Id == \"2\" || r.Id == \"3\" || r.Id == \"4\" {\n\t\t\t\treturn ErrRealmIdNotAllowed\n\t\t\t}\n\t\t}\n\t}\n\t\n\t_, removed := zrm.Realms.Remove(r.Id)\n\tif !removed {\n\t\treturn ErrRealmNotRemoved\n\t}\n\treturn nil\n\t\n}\n\n// getters\nfunc (zrm *ZRealmManager) GetRealmById(realmId string) (r Realm, err error) {\n\t// implementation\n\tif zrm.Realms.Size() != 0 {\n\t\trInterface, exist := zrm.Realms.Get(realmId)\n\t\tif exist {\n\t\t\treturn rInterface.(Realm), nil\n\t\t} else {\n\t\t\treturn Realm{}, ErrRealmIdNotFound\n\t\t}\n\t}\n\treturn Realm{}, ErrRealmIdNotFound\n}\n\nfunc (zrm *ZRealmManager) GetRealms() (realms string, err error) {\n\t// implementation\n\tvar allRealms []Realm\n\n\t// Iterate over the Realms AVL tree to collect all Context objects.\n\tzrm.Realms.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif realm, ok := value.(Realm); ok {\n\t\t\tallRealms = append(allRealms, realm)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\n\t// Create a RealmsObject with all collected contexts.\n\trealmsObject := &RealmsObject{\n\t\tRealms: allRealms,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the realms into JSON.\n\tmarshalledRealms, rerr := realmsObject.MarshalJSON()\n\tif rerr != nil {\n\t\treturn \"\", rerr\n\t} \n\treturn string(marshalledRealms), nil\n}\n" + }, + { + "name": "tasks.gno", + "body": "package zentasktic\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype Task struct {\n\tId \t\t\tstring `json:\"taskId\"`\n\tProjectId \tstring `json:\"taskProjectId\"`\n\tContextId\tstring `json:\"taskContextId\"`\n\tRealmId \tstring `json:\"taskRealmId\"`\n\tBody \t\tstring `json:\"taskBody\"`\n\tDue\t\t\tstring `json:\"taskDue\"`\n\tAlert\t\tstring `json:\"taskAlert\"`\n}\n\ntype ZTaskManager struct {\n\tTasks *avl.Tree\n}\n\nfunc NewZTaskManager() *ZTaskManager {\n\treturn &ZTaskManager{\n\t\tTasks: avl.NewTree(),\n\t}\n}\n\n// actions\n\nfunc (ztm *ZTaskManager) AddTask(t Task) error {\n\tif ztm.Tasks.Size() != 0 {\n\t\t_, exist := ztm.Tasks.Get(t.Id)\n\t\tif exist {\n\t\t\treturn ErrTaskIdAlreadyExists\n\t\t}\n\t}\n\tztm.Tasks.Set(t.Id, t)\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) RemoveTask(t Task) error {\n\texistingTaskInterface, exist := ztm.Tasks.Get(t.Id)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\texistingTask := existingTaskInterface.(Task)\n\n\t // task is removable only in Asses (RealmId 1) or via a Collection (RealmId 4)\n\tif existingTask.RealmId != \"1\" && existingTask.RealmId != \"4\" {\n\t\treturn ErrTaskNotRemovable\n\t}\n\n\t_, removed := ztm.Tasks.Remove(existingTask.Id)\n\tif !removed {\n\t\treturn ErrTaskNotRemoved\n\t}\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) EditTask(t Task) error {\n\texistingTaskInterface, exist := ztm.Tasks.Get(t.Id)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\texistingTask := existingTaskInterface.(Task)\n\n\t// task Body is editable only when task is in Assess, RealmId = \"1\"\n\tif t.RealmId != \"1\" {\n\t\tif t.Body != existingTask.Body {\n\t\t\treturn ErrTaskNotInAssessRealm\n\t\t}\n\t}\n\n\tztm.Tasks.Set(t.Id, t)\n\treturn nil\n}\n\n// Helper function to move a task to a different realm\nfunc (ztm *ZTaskManager) MoveTaskToRealm(taskId, realmId string) error {\n\texistingTaskInterface, exist := ztm.Tasks.Get(taskId)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\texistingTask := existingTaskInterface.(Task)\n\texistingTask.RealmId = realmId\n\tztm.Tasks.Set(taskId, existingTask)\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) SetTaskDueDate(taskId, dueDate string) error {\n\ttaskInterface, exist := ztm.Tasks.Get(taskId)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\ttask := taskInterface.(Task)\n\n\tif task.RealmId == \"2\" {\n\t\ttask.Due = dueDate\n\t\tztm.Tasks.Set(task.Id, task)\n\t} else {\n\t\treturn ErrTaskNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) SetTaskAlert(taskId, alertDate string) error {\n\ttaskInterface, exist := ztm.Tasks.Get(taskId)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\ttask := taskInterface.(Task)\n\n\tif task.RealmId == \"2\" {\n\t\ttask.Alert = alertDate\n\t\tztm.Tasks.Set(task.Id, task)\n\t} else {\n\t\treturn ErrTaskNotEditable\n\t}\n\n\treturn nil\n}\n\n// tasks & projects association\n\nfunc (zpm *ZProjectManager) AttachTaskToProject(ztm *ZTaskManager, t Task, p Project) error {\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(p.Id)\n\tif !texist {\n\t\texistingProject.Tasks = []Task{}\n\t} else {\n\t\ttasks, ok := existingProjectTasksInterface.([]Task)\n\t\tif !ok {\n\t\t\treturn ErrProjectTasksNotFound\n\t\t}\n\t\texistingProject.Tasks = tasks\n\t}\n\n\tt.ProjectId = p.Id\n\t// @todo we need to remove it from Tasks if it was previously added there, then detached\n\texistingTask, err := ztm.GetTaskById(t.Id)\n\tif err == nil {\n\t\tztm.RemoveTask(existingTask)\n\t}\n\tupdatedTasks := append(existingProject.Tasks, t)\n\tzpm.ProjectTasks.Set(p.Id, updatedTasks)\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) EditProjectTask(projectTaskId string, projectTaskBody string, projectId string) error {\n existingProjectInterface, exist := zpm.Projects.Get(projectId)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n\tif existingProject.RealmId != \"1\" {\n\t\treturn ErrProjectNotEditable\n\t}\n\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n existingProject.Tasks = tasks\n\n var index int = -1\n for i, task := range existingProject.Tasks {\n if task.Id == projectTaskId {\n index = i\n break\n }\n }\n\n if index != -1 {\n existingProject.Tasks[index].Body = projectTaskBody\n } else {\n return ErrTaskByIdNotFound\n }\n\n zpm.ProjectTasks.Set(projectId, existingProject.Tasks)\n return nil\n}\n\nfunc (zpm *ZProjectManager) DetachTaskFromProject(ztm *ZTaskManager, projectTaskId string, detachedTaskId string, p Project) error {\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(p.Id)\n\tif !texist {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\ttasks, ok := existingProjectTasksInterface.([]Task)\n\tif !ok {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\texistingProject.Tasks = tasks\n\n\tvar foundTask Task\n\tvar index int = -1\n\tfor i, task := range existingProject.Tasks {\n\t\tif task.Id == projectTaskId {\n\t\t\tindex = i\n\t\t\tfoundTask = task\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != -1 {\n\t\texistingProject.Tasks = append(existingProject.Tasks[:index], existingProject.Tasks[index+1:]...)\n\t} else {\n\t\treturn ErrTaskByIdNotFound\n\t}\n\n\tfoundTask.ProjectId = \"\"\n\tfoundTask.Id = detachedTaskId\n\t// Tasks and ProjectTasks have different storage, if a task is detached from a Project\n\t// we add it to the Tasks storage\n\tif err := ztm.AddTask(foundTask); err != nil {\n\t\treturn err\n\t}\n\n\tzpm.ProjectTasks.Set(p.Id, existingProject.Tasks)\n\treturn nil\n}\n\n\nfunc (zpm *ZProjectManager) RemoveTaskFromProject(projectTaskId string, projectId string) error {\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n\tif !texist {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\ttasks, ok := existingProjectTasksInterface.([]Task)\n\tif !ok {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\texistingProject.Tasks = tasks\n\n\tvar index int = -1\n\tfor i, task := range existingProject.Tasks {\n\t\tif task.Id == projectTaskId {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != -1 {\n\t\texistingProject.Tasks = append(existingProject.Tasks[:index], existingProject.Tasks[index+1:]...)\n\t} else {\n\t\treturn ErrTaskByIdNotFound\n\t}\n\n\tzpm.ProjectTasks.Set(projectId, existingProject.Tasks)\n\treturn nil\n}\n\n// getters\n\nfunc (ztm *ZTaskManager) GetTaskById(taskId string) (Task, error) {\n\tif ztm.Tasks.Size() != 0 {\n\t\ttInterface, exist := ztm.Tasks.Get(taskId)\n\t\tif exist {\n\t\t\treturn tInterface.(Task), nil\n\t\t}\n\t}\n\treturn Task{}, ErrTaskIdNotFound\n}\n\nfunc (ztm *ZTaskManager) GetAllTasks() (task string) {\n\tvar allTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif task, ok := value.(Task); ok {\n\t\t\tallTasks = append(allTasks, task)\n\t\t}\n\t\treturn false\n\t})\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: allTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\t\n}\n\nfunc (ztm *ZTaskManager) GetTasksByRealm(realmId string) (tasks string) {\n\tvar realmTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif task, ok := value.(Task); ok {\n\t\t\tif task.RealmId == realmId {\n\t\t\t\trealmTasks = append(realmTasks, task)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: realmTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\n}\n\nfunc (ztm *ZTaskManager) GetTasksByContextAndRealm(contextId string, realmId string) (tasks string) {\n\tvar contextTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif task, ok := value.(Task); ok {\n\t\t\tif task.ContextId == contextId && task.ContextId == realmId {\n\t\t\t\tcontextTasks = append(contextTasks, task)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: contextTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\n}\n\nfunc (ztm *ZTaskManager) GetTasksByDate(taskDate string, filterType string) (tasks string) {\n\tparsedDate, err := time.Parse(\"2006-01-02\", taskDate)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tvar filteredTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttask, ok := value.(Task)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tstoredDate, serr := time.Parse(\"2006-01-02\", task.Due)\n\t\tif serr != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch filterType {\n\t\tcase \"specific\":\n\t\t\tif storedDate.Format(\"2006-01-02\") == parsedDate.Format(\"2006-01-02\") {\n\t\t\t\tfilteredTasks = append(filteredTasks, task)\n\t\t\t}\n\t\tcase \"before\":\n\t\t\tif storedDate.Before(parsedDate) {\n\t\t\t\tfilteredTasks = append(filteredTasks, task)\n\t\t\t}\n\t\tcase \"after\":\n\t\t\tif storedDate.After(parsedDate) {\n\t\t\t\tfilteredTasks = append(filteredTasks, task)\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t})\n\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: filteredTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\n}\n" + }, + { + "name": "tasks_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n\n// Shared instance of ZTaskManager\nvar ztm *ZTaskManager\n\nfunc init() {\n ztm = NewZTaskManager()\n}\n\nfunc Test_AddTask(t *testing.T) {\n task := Task{Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",}\n\n // Test adding a task successfully.\n err := ztm.AddTask(task)\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n // Test adding a duplicate task.\n cerr := ztm.AddTask(task)\n if cerr != ErrTaskIdAlreadyExists {\n t.Errorf(\"Expected ErrTaskIdAlreadyExists, got %v\", cerr)\n }\n}\n\nfunc Test_RemoveTask(t *testing.T) {\n \n task := Task{Id: \"20\", Body: \"Removable task\", RealmId: \"1\"}\n\n // Test adding a task successfully.\n err := ztm.AddTask(task)\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n retrievedTask, rerr := ztm.GetTaskById(task.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added task\")\n }\n\n // Test removing a task\n terr := ztm.RemoveTask(retrievedTask)\n if terr != nil {\n t.Errorf(\"Expected nil, got %v\", terr)\n }\n}\n\nfunc Test_EditTask(t *testing.T) {\n \n task := Task{Id: \"2\", Body: \"First content\", RealmId: \"1\", ContextId: \"2\"}\n\n // Test adding a task successfully.\n err := ztm.AddTask(task)\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n // Test editing the task\n editedTask := Task{Id: task.Id, Body: \"Edited content\", RealmId: task.RealmId, ContextId: \"2\"}\n cerr := ztm.EditTask(editedTask)\n if cerr != nil {\n t.Errorf(\"Failed to edit the task\")\n }\n\n retrievedTask, _ := ztm.GetTaskById(editedTask.Id)\n if retrievedTask.Body != \"Edited content\" {\n t.Errorf(\"Task was not edited\")\n }\n}\n/*\nfunc Test_MoveTaskToRealm(t *testing.T) {\n \n task := Task{Id: \"3\", Body: \"First content\", RealmId: \"1\", ContextId: \"1\"}\n\n // Test adding a task successfully.\n err := task.AddTask()\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n // Test moving the task to another realm\n \n cerr := task.MoveTaskToRealm(\"2\")\n if cerr != nil {\n t.Errorf(\"Failed to move task to another realm\")\n }\n\n retrievedTask, _ := GetTaskById(task.Id)\n if retrievedTask.RealmId != \"2\" {\n t.Errorf(\"Task was moved to the wrong realm\")\n }\n}\n\nfunc Test_AttachTaskToProject(t *testing.T) {\n \n // Example Projects and Tasks\n prj := Project{Id: \"1\", Body: \"Project 1\", RealmId: \"1\",}\n tsk := Task{Id: \"4\", Body: \"Task 4\", RealmId: \"1\",}\n\n Projects.Set(prj.Id, prj) // Mock existing project\n\n tests := []struct {\n name string\n project Project\n task Task\n wantErr bool\n errMsg error\n }{\n {\n name: \"Attach to existing project\",\n project: prj,\n task: tsk,\n wantErr: false,\n },\n {\n name: \"Attach to non-existing project\",\n project: Project{Id: \"200\", Body: \"Project 200\", RealmId: \"1\",},\n task: tsk,\n wantErr: true,\n errMsg: ErrProjectIdNotFound,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := tt.task.AttachTaskToProject(tt.project)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"AttachTaskToProject() error = %v, wantErr %v\", err, tt.wantErr)\n }\n if tt.wantErr && err != tt.errMsg {\n t.Errorf(\"AttachTaskToProject() error = %v, expected %v\", err, tt.errMsg)\n }\n\n // For successful attach, verify the task is added to the project's tasks.\n if !tt.wantErr {\n tasks, exist := ProjectTasks.Get(tt.project.Id)\n if !exist || len(tasks.([]Task)) == 0 {\n t.Errorf(\"Task was not attached to the project\")\n } else {\n found := false\n for _, task := range tasks.([]Task) {\n if task.Id == tt.task.Id {\n found = true\n break\n }\n }\n if !found {\n t.Errorf(\"Task was not attached to the project\")\n }\n }\n }\n })\n }\n}\n\nfunc TestDetachTaskFromProject(t *testing.T) {\n\t\n\t// Setup:\n\tproject := Project{Id: \"p1\", Body: \"Test Project\"}\n\ttask1 := Task{Id: \"5\", Body: \"Task One\"}\n\ttask2 := Task{Id: \"6\", Body: \"Task Two\"}\n\n\tProjects.Set(project.Id, project)\n\tProjectTasks.Set(project.Id, []Task{task1, task2})\n\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\tproject Project\n\t\twantErr bool\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname: \"Detach existing task from project\",\n\t\t\ttask: task1,\n\t\t\tproject: project,\n\t\t\twantErr: false,\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to detach task from non-existing project\",\n\t\t\ttask: task1,\n\t\t\tproject: Project{Id: \"nonexistent\"},\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrProjectIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to detach non-existing task from project\",\n\t\t\ttask: Task{Id: \"nonexistent\"},\n\t\t\tproject: project,\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrTaskByIdNotFound,\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.task.DetachTaskFromProject(tt.project)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil || err != tt.expectedErr {\n\t\t\t\t\tt.Errorf(\"%s: expected error %v, got %v\", tt.name, tt.expectedErr, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: unexpected error: %v\", tt.name, err)\n\t\t\t\t}\n\n\t\t\t\t// For successful detachment, verify the task is no longer part of the project's tasks\n\t\t\t\tif !tt.wantErr {\n\t\t\t\t\ttasks, _ := ProjectTasks.Get(tt.project.Id)\n\t\t\t\t\tfor _, task := range tasks.([]Task) {\n\t\t\t\t\t\tif task.Id == tt.task.Id {\n\t\t\t\t\t\t\tt.Errorf(\"%s: task was not detached from the project\", tt.name)\n\t\t\t\t\t\t\tbreak\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}\n}\n\nfunc Test_SetTaskDueDate(t *testing.T) {\n\ttaskRealmIdOne, _ := GetTaskById(\"1\")\n taskRealmIdTwo, _ := GetTaskById(\"10\") \n\t// Define test cases\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\tdueDate string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"Task does not exist\",\n\t\t\ttask: Task{Id: \"nonexistent\", RealmId: \"2\"},\n\t\t\twantErr: ErrTaskIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Task not editable due to wrong realm\",\n\t\t\ttask: taskRealmIdOne,\n\t\t\twantErr: ErrTaskNotEditable,\n\t\t},\n\t\t{\n\t\t\tname: \"Successfully set due date\",\n\t\t\ttask: taskRealmIdTwo,\n\t\t\tdueDate: \"2023-01-01\",\n\t\t\twantErr: nil,\n\t\t},\n\t}\n\n\t// Execute test cases\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := tc.task.SetTaskDueDate(tc.dueDate)\n\n\t\t\t// Validate\n\t\t\tif err != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error %v, got %v\", tc.wantErr, err)\n\t\t\t}\n\n\t\t\t// Additional check for the success case to ensure the due date was actually set\n\t\t\tif err == nil {\n\t\t\t\t// Fetch the task again to check if the due date was set correctly\n\t\t\t\tupdatedTask, exist := Tasks.Get(tc.task.Id)\n\t\t\t\tif !exist {\n\t\t\t\t\tt.Fatalf(\"Task %v was not found after setting the due date\", tc.task.Id)\n\t\t\t\t}\n\t\t\t\tif updatedTask.(Task).Due != tc.dueDate {\n\t\t\t\t\tt.Errorf(\"Expected due date to be %v, got %v\", tc.dueDate, updatedTask.(Task).Due)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_SetTaskAlert(t *testing.T) {\n\ttaskRealmIdOne, _ := GetTaskById(\"1\")\n taskRealmIdTwo, _ := GetTaskById(\"10\") \n\t// Define test cases\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\talertDate string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"Task does not exist\",\n\t\t\ttask: Task{Id: \"nonexistent\", RealmId: \"2\"},\n\t\t\twantErr: ErrTaskIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Task not editable due to wrong realm\",\n\t\t\ttask: taskRealmIdOne,\n\t\t\twantErr: ErrTaskNotEditable,\n\t\t},\n\t\t{\n\t\t\tname: \"Successfully set alert\",\n\t\t\ttask: taskRealmIdTwo,\n\t\t\talertDate: \"2024-01-01\",\n\t\t\twantErr: nil,\n\t\t},\n\t}\n\n\t// Execute test cases\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := tc.task.SetTaskAlert(tc.alertDate)\n\n\t\t\t// Validate\n\t\t\tif err != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error %v, got %v\", tc.wantErr, err)\n\t\t\t}\n\n\t\t\t// Additional check for the success case to ensure the due date was actually set\n\t\t\tif err == nil {\n\t\t\t\t// Fetch the task again to check if the due date was set correctly\n\t\t\t\tupdatedTask, exist := Tasks.Get(tc.task.Id)\n\t\t\t\tif !exist {\n\t\t\t\t\tt.Fatalf(\"Task %v was not found after setting the due date\", tc.task.Id)\n\t\t\t\t}\n\t\t\t\tif updatedTask.(Task).Alert != tc.alertDate {\n\t\t\t\t\tt.Errorf(\"Expected due date to be %v, got %v\", tc.alertDate, updatedTask.(Task).Due)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// getters\n\nfunc Test_GetAllTasks(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n knownTasks := []Task{\n {Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",},\n {Id: \"10\", Body: \"First content\", RealmId: \"2\", ContextId: \"2\", Due: \"2023-01-01\", Alert: \"2024-01-01\"},\n {Id: \"2\", Body: \"Edited content\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable task\", RealmId: \"1\",},\n {Id: \"3\", Body: \"First content\", RealmId: \"2\", ContextId: \"1\",},\n {Id: \"40\", Body: \"Task 40\", RealmId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n tasksObject := TasksObject{Tasks: knownTasks}\n expected, err := tasksObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known tasks: %v\", err)\n }\n\n // Execute GetAllTasks() to get the actual outcome.\n actual, err := GetAllTasks()\n if err != nil {\n t.Fatalf(\"GetAllTasks() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual task JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetTasksByDate(t *testing.T) {\n\t\n\ttests := []struct {\n\t\tname string\n\t\ttaskDate string\n\t\tfilterType string\n\t\twant string\n\t\twantErr bool\n\t}{\n\t\t{\"SpecificDate\", \"2023-01-01\", \"specific\", `{\"tasks\":[{\"taskId\":\"10\",\"taskProjectId\":\"\",\"taskContextId\":\"2\",\"taskRealmId\":\"2\",\"taskBody\":\"First content\",\"taskDue\":\"2023-01-01\",\"taskAlert\":\"2024-01-01\"}]}`, false},\n\t\t{\"BeforeDate\", \"2022-04-05\", \"before\", \"\", false},\n\t\t{\"AfterDate\", \"2023-04-05\", \"after\", \"\", false},\n\t\t{\"NoMatch\", \"2002-04-07\", \"specific\", \"\", false},\n\t\t{\"InvalidDateFormat\", \"April 5, 2023\", \"specific\", \"\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := GetTasksByDate(tt.taskDate, tt.filterType)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetTasksByDate() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && got != tt.want {\n\t\t\t\tt.Errorf(\"GetTasksByDate() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_GetTaskById(t *testing.T){\n // test getting a non-existing task\n nonTask, err := GetTaskById(\"0\")\n if err != ErrTaskByIdNotFound {\n t.Fatalf(\"Expected ErrTaskByIdNotFound, got: %v\", err)\n }\n\n // test getting the correct task by id\n correctTask, err := GetTaskById(\"1\")\n if err != nil {\n t.Fatalf(\"Failed to get task by id, error: %v\", err)\n }\n\n if correctTask.Body != \"First task\" {\n t.Fatalf(\"Got the wrong task, with body: %v\", correctTask.Body)\n }\n}\n\nfunc Test_GetTasksByRealm(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n tasksInAssessRealm := []Task{\n {Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",},\n {Id: \"2\", RealmId: \"1\", Body: \"Edited content\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable task\", RealmId: \"1\",},\n {Id: \"40\", Body: \"Task 40\", RealmId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n tasksObjectAssess := TasksObject{Tasks: tasksInAssessRealm}\n expected, err := tasksObjectAssess.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal tasks in Assess: %v\", err)\n }\n\n actual, err := GetTasksByRealm(\"1\")\n if err != nil {\n t.Fatalf(\"GetTasksByRealm('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual task JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetTasksByContext(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n tasksInContextOne := []Task{\n {Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",},\n {Id: \"3\", RealmId: \"2\", Body: \"First content\", ContextId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n tasksObjectForContexts := TasksObject{Tasks: tasksInContextOne}\n expected, err := tasksObjectForContexts.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal tasks for ContextId 1: %v\", err)\n }\n\n actual, err := GetTasksByContext(\"1\")\n if err != nil {\n t.Fatalf(\"GetTasksByContext('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual task JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "1000000", + "gas_fee": "1000000ugnot" + }, + "signatures": [], + "memo": "" +} + +-- tx2.tx -- +{ + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "zentasktic", + "path": "gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic", + "files": [ + { + "name": "README.md", + "body": "# ZenTasktic Core\n\nA basic, minimalisitc Asess-Decide-Do implementations as `p/zentasktic`. The diagram below shows a simplified ADD workflow.\n\n![ZenTasktic](ZenTasktic-framework.png)\n\nThis implementation will expose all the basic features of the framework: tasks & projects with complete workflows. Ideally, this should offer all the necessary building blocks for any other custom implementation.\n\n## Object Definitions and Default Values\n\nAs an unopinionated ADD workflow, `zentastic_core` defines the following objects:\n\n- Realm\n\nRealms act like containers for tasks & projects during their journey from Assess to Do, via Decide. Each realm has a certain restrictions, e.g. a task's Body can only be edited in Assess, a Context, Due date and Alert can only be added in Decide, etc.\n\nIf someone observes different realms, there is support for adding and removing arbitrary Realms.\n\n_note: the Ids between 1 and 4 are reserved for: 1-Assess, 2-Decide, 3-Do, 4-Collection. Trying to add or remove such a Realm will raise an error._\n\n\nRealm data definition:\n\n```\ntype Realm struct {\n\tId \t\t\tstring `json:\"realmId\"`\n\tName \t\tstring `json:\"realmName\"`\n}\n```\n\n- Task\n\nA task is the minimal data structure in ZenTasktic, with the following definition:\n\n```\ntype Task struct {\n\tId \t\t\tstring `json:\"taskId\"`\n\tProjectId \tstring `json:\"taskProjectId\"`\n\tContextId\tstring `json:\"taskContextId\"`\n\tRealmId \tstring `json:\"taskRealmId\"`\n\tBody \t\tstring `json:\"taskBody\"`\n\tDue\t\t\tstring `json:\"taskDue\"`\n\tAlert\t\tstring `json:\"taskAlert\"`\n}\n```\n\n- Project\n\nProjects are unopinionated collections of Tasks. A Task in a Project can be in any Realm, but the restrictions are propagated upwards to the Project: e.g. if a Task is marked as 'done' in the Do realm (namely changing its RealmId property to \"1\", Assess, or \"4\" Collection), and the rest of the tasks are not, the Project cannot be moved back to Decide or Asses, all Tasks must have consisted RealmId properties.\n\nA Task can be arbitrarily added to, removed from and moved to another Project.\n\nProject data definition:\n\n\n```\ntype Project struct {\n\tId \t\t\tstring `json:\"projectId\"`\n\tContextId\tstring `json:\"projectContextId\"`\n\tRealmId \tstring `json:\"projectRealmId\"`\n\tTasks\t\t[]Task `json:\"projectTasks\"`\n\tBody \t\tstring `json:\"projectBody\"`\n\tDue\t\t\tstring `json:\"ProjectDue\"`\n}\n```\n\n\n- Context\n\nContexts act as tags, grouping together Tasks and Project, e.g. \"Backend\", \"Frontend\", \"Marketing\". Contexts have no defaults and can be added or removed arbitrarily.\n\nContext data definition:\n\n```\ntype Context struct {\n\tId \t\t\tstring `json:\"contextId\"`\n\tName \t\tstring `json:\"contextName\"`\n}\n```\n\n- Collection\n\nCollections are intended as an agnostic storage for Tasks & Projects which are either not ready to be Assessed, or they have been already marked as done, and, for whatever reason, they need to be kept in the system. There is a special Realm Id for Collections, \"4\", although technically they are not part of the Assess-Decide-Do workflow.\n\nCollection data definition:\n\n```\ntype Collection struct {\n\tId \t\t\tstring `json:\"collectionId\"`\n\tRealmId \tstring `json:\"collectionRealmId\"`\n\tName \t\tstring `json:\"collectionName\"`\n\tTasks\t\t[]Task `json:\"collectionTasks\"`\n\tProjects\t[]Project `json:\"collectionProjects\"`\n}\n```\n\n- ObjectPath\n\nObjectPaths are minimalistic representations of the journey taken by a Task or a Project in the Assess-Decide-Do workflow. By recording their movement between various Realms, one can extract their `ZenStatus`, e.g., if a Task has been moved many times between Assess and Decide, never making it to Do, we can infer the following:\n-- either the Assess part was incomplete\n-- the resources needed for that Task are not yet ready\n\nObjectPath data definition:\n\n```\ntype ObjectPath struct {\n\tObjectType\tstring `json:\"objectType\"` // Task, Project\n\tId \t\t\tstring `json:\"id\"` // this is the Id of the object moved, Task, Project\n\tRealmId \tstring `json:\"realmId\"`\n}\n```\n\n_note: the core implementation offers the basic adding and retrieving functionality, but it's up to the client realm using the `zentasktic` package to call them when an object is moved from one Realm to another._\n\n## Example Workflow\n\n```\npackage example_zentasktic\n\nimport \"gno.land/p/demo/zentasktic\"\n\nvar ztm *zentasktic.ZTaskManager\nvar zpm *zentasktic.ZProjectManager\nvar zrm *zentasktic.ZRealmManager\nvar zcm *zentasktic.ZContextManager\nvar zcl *zentasktic.ZCollectionManager\nvar zom *zentasktic.ZObjectPathManager\n\nfunc init() {\n ztm = zentasktic.NewZTaskManager()\n zpm = zentasktic.NewZProjectManager()\n\tzrm = zentasktic.NewZRealmManager()\n\tzcm = zentasktic.NewZContextManager()\n\tzcl = zentasktic.NewZCollectionManager()\n\tzom = zentasktic.NewZObjectPathManager()\n}\n\n// initializing a task, assuming we get the value POSTed by some call to the current realm\n\nnewTask := zentasktic.Task{Id: \"20\", Body: \"Buy milk\"}\nztm.AddTask(newTask)\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"1\"}\nzom.AddPath(taskPath)\n...\n\neditedTask := zentasktic.Task{Id: \"20\", Body: \"Buy fresh milk\"}\nztm.EditTask(editedTask)\n\n...\n\n// moving it to Decide\n\nztm.MoveTaskToRealm(\"20\", \"2\")\n\n// adding context, due date and alert, assuming they're received from other calls\n\nshoppingContext := zcm.GetContextById(\"2\")\n\ncerr := zcm.AddContextToTask(ztm, shoppingContext, editedTask)\n\nderr := ztm.SetTaskDueDate(editedTask.Id, \"2024-04-10\")\nnow := time.Now() // replace with the actual time of the alert\nalertTime := now.Format(\"2006-01-02 15:04:05\")\naerr := ztm.SetTaskAlert(editedTask.Id, alertTime)\n\n...\n\n// move the Task to Do\n\nztm.MoveTaskToRealm(editedTask.Id, \"2\")\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"2\"}\nzom.AddPath(taskPath)\n\n// after the task is done, we sent it back to Assess\n\nztm.MoveTaskToRealm(editedTask.Id,\"1\")\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"1\"}\nzom.AddPath(taskPath)\n\n// from here, we can add it to a collection\n\nmyCollection := zcm.GetCollectionById(\"1\")\n\nzcm.AddTaskToCollection(ztm, myCollection, editedTask)\n\n// if we want to keep track of the object zen status, we update the object path\ntaskPath := zentasktic.ObjectPath{ObjectType: \"task\", Id: \"20\", RealmId: \"4\"}\nzom.AddPath(taskPath)\n\n```\n\nAll tests are in the `*_test.gno` files, e.g. `tasks_test.gno`, `projects_test.gno`, etc." + }, + { + "name": "collections.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\n\ntype Collection struct {\n\tId \t\t\tstring `json:\"collectionId\"`\n\tRealmId \tstring `json:\"collectionRealmId\"`\n\tName \t\tstring `json:\"collectionName\"`\n\tTasks\t\t[]Task `json:\"collectionTasks\"`\n\tProjects\t[]Project `json:\"collectionProjects\"`\n}\n\ntype ZCollectionManager struct {\n\tCollections *avl.Tree \n\tCollectionTasks *avl.Tree\n\tCollectionProjects *avl.Tree \n}\n\nfunc NewZCollectionManager() *ZCollectionManager {\n return &ZCollectionManager{\n Collections: avl.NewTree(),\n CollectionTasks: avl.NewTree(),\n CollectionProjects: avl.NewTree(),\n }\n}\n\n\n// actions\n\nfunc (zcolm *ZCollectionManager) AddCollection(c Collection) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif exist {\n\t\t\treturn ErrCollectionIdAlreadyExists\n\t\t}\n\t}\n\tzcolm.Collections.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) EditCollection(c Collection) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\t\n\tzcolm.Collections.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) RemoveCollection(c Collection) (err error) {\n // implementation\n if zcolm.Collections.Size() != 0 {\n collectionInterface, exist := zcolm.Collections.Get(c.Id)\n if !exist {\n return ErrCollectionIdNotFound\n }\n collection := collectionInterface.(Collection)\n\n _, removed := zcolm.Collections.Remove(collection.Id)\n if !removed {\n return ErrCollectionNotRemoved\n }\n\n if zcolm.CollectionTasks.Size() != 0 {\n _, removedTasks := zcolm.CollectionTasks.Remove(collection.Id)\n if !removedTasks {\n return ErrCollectionNotRemoved\n }\t\n }\n\n if zcolm.CollectionProjects.Size() != 0 {\n _, removedProjects := zcolm.CollectionProjects.Remove(collection.Id)\n if !removedProjects {\n return ErrCollectionNotRemoved\n }\t\n }\n }\n return nil\n}\n\n\nfunc (zcolm *ZCollectionManager) AddProjectToCollection(zpm *ZProjectManager, c Collection, p Project) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionProjects, texist := zcolm.CollectionProjects.Get(c.Id)\n\tif !texist {\n\t\t// If the collections has no projects yet, initialize the slice.\n\t\texistingCollectionProjects = []Project{}\n\t} else {\n\t\tprojects, ok := existingCollectionProjects.([]Project)\n\t\tif !ok {\n\t\t\treturn ErrCollectionsProjectsNotFound\n\t\t}\n\t\texistingCollectionProjects = projects\n\t}\n\tp.RealmId = \"4\"\n\tif err := zpm.EditProject(p); err != nil {\n\t\treturn err\n\t}\n\tupdatedProjects := append(existingCollectionProjects.([]Project), p)\n\tzcolm.CollectionProjects.Set(c.Id, updatedProjects)\n\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) AddTaskToCollection(ztm *ZTaskManager, c Collection, t Task) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionTasks, texist := zcolm.CollectionTasks.Get(c.Id)\n\tif !texist {\n\t\t// If the collections has no tasks yet, initialize the slice.\n\t\texistingCollectionTasks = []Task{}\n\t} else {\n\t\ttasks, ok := existingCollectionTasks.([]Task)\n\t\tif !ok {\n\t\t\treturn ErrCollectionsTasksNotFound\n\t\t}\n\t\texistingCollectionTasks = tasks\n\t}\n\tt.RealmId = \"4\"\n\tif err := ztm.EditTask(t); err != nil {\n\t\treturn err\n\t}\n\tupdatedTasks := append(existingCollectionTasks.([]Task), t)\n\tzcolm.CollectionTasks.Set(c.Id, updatedTasks)\n\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) RemoveProjectFromCollection(zpm *ZProjectManager, c Collection, p Project) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionProjects, texist := zcolm.CollectionProjects.Get(c.Id)\n\tif !texist {\n\t\t// If the collection has no projects yet, return appropriate error\n\t\treturn ErrCollectionsProjectsNotFound\n\t}\n\n\t// Find the index of the project to be removed.\n\tvar index int = -1\n\tfor i, project := range existingCollectionProjects.([]Project) {\n\t\tif project.Id == p.Id {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// If the project was found, we remove it from the slice.\n\tif index != -1 {\n\t\t// by default we send it back to Assess\n\t\tp.RealmId = \"1\"\n\t\tzpm.EditProject(p)\n\t\texistingCollectionProjects = append(existingCollectionProjects.([]Project)[:index], existingCollectionProjects.([]Project)[index+1:]...)\n\t} else {\n\t\t// Project not found in the collection\n\t\treturn ErrProjectByIdNotFound \n\t}\n\tzcolm.CollectionProjects.Set(c.Id, existingCollectionProjects)\n\n\treturn nil\n}\n\nfunc (zcolm *ZCollectionManager) RemoveTaskFromCollection(ztm *ZTaskManager, c Collection, t Task) (err error) {\n\t// implementation\n\tif zcolm.Collections.Size() != 0 {\n\t\t_, exist := zcolm.Collections.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrCollectionIdNotFound\n\t\t}\n\t}\n\n\texistingCollectionTasks, texist := zcolm.CollectionTasks.Get(c.Id)\n\tif !texist {\n\t\t// If the collection has no tasks yet, return appropriate error\n\t\treturn ErrCollectionsTasksNotFound\n\t}\n\n\t// Find the index of the task to be removed.\n\tvar index int = -1\n\tfor i, task := range existingCollectionTasks.([]Task) {\n\t\tif task.Id == t.Id {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// If the task was found, we remove it from the slice.\n\tif index != -1 {\n\t\t// by default, we send the task to Assess\n\t\tt.RealmId = \"1\"\n\t\tztm.EditTask(t)\n\t\texistingCollectionTasks = append(existingCollectionTasks.([]Task)[:index], existingCollectionTasks.([]Task)[index+1:]...)\n\t} else {\n\t\t// Task not found in the collection\n\t\treturn ErrTaskByIdNotFound \n\t}\n\tzcolm.CollectionTasks.Set(c.Id, existingCollectionTasks)\n\n\treturn nil\n}\n\n// getters\n\nfunc (zcolm *ZCollectionManager) GetCollectionById(collectionId string) (Collection, error) {\n if zcolm.Collections.Size() != 0 {\n cInterface, exist := zcolm.Collections.Get(collectionId)\n if exist {\n collection := cInterface.(Collection)\n // look for collection Tasks, Projects\n existingCollectionTasks, texist := zcolm.CollectionTasks.Get(collectionId)\n if texist {\n collection.Tasks = existingCollectionTasks.([]Task)\n }\n existingCollectionProjects, pexist := zcolm.CollectionProjects.Get(collectionId)\n if pexist {\n collection.Projects = existingCollectionProjects.([]Project)\n }\n return collection, nil\n }\n return Collection{}, ErrCollectionByIdNotFound\n }\n return Collection{}, ErrCollectionByIdNotFound\n}\n\nfunc (zcolm *ZCollectionManager) GetCollectionTasks(c Collection) (tasks []Task, err error) {\n\t\n\tif zcolm.CollectionTasks.Size() != 0 {\n\t\ttask, exist := zcolm.CollectionTasks.Get(c.Id)\n\t\tif !exist {\n\t\t\t// if there's no record in CollectionTasks, we don't have to return anything\n\t\t\treturn nil, ErrCollectionsTasksNotFound\n\t\t} else {\n\t\t\t// type assertion to convert interface{} to []Task\n\t\t\texistingCollectionTasks, ok := task.([]Task)\n\t\t\tif !ok {\n\t\t\t\treturn nil, ErrTaskFailedToAssert\n\t\t\t}\n\t\t\treturn existingCollectionTasks, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc (zcolm *ZCollectionManager) GetCollectionProjects(c Collection) (projects []Project, err error) {\n\t\n\tif zcolm.CollectionProjects.Size() != 0 {\n\t\tproject, exist := zcolm.CollectionProjects.Get(c.Id)\n\t\tif !exist {\n\t\t\t// if there's no record in CollectionProjets, we don't have to return anything\n\t\t\treturn nil, ErrCollectionsProjectsNotFound\n\t\t} else {\n\t\t\t// type assertion to convert interface{} to []Projet\n\t\t\texistingCollectionProjects, ok := project.([]Project)\n\t\t\tif !ok {\n\t\t\t\treturn nil, ErrProjectFailedToAssert\n\t\t\t}\n\t\t\treturn existingCollectionProjects, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc (zcolm *ZCollectionManager) GetAllCollections() (collections string, err error) {\n\t// implementation\n\tvar allCollections []Collection\n\t\n\t// Iterate over the Collections AVL tree to collect all Project objects.\n\t\n\tzcolm.Collections.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif collection, ok := value.(Collection); ok {\n\t\t\t// get collection tasks, if any\n\t\t\tcollectionTasks, _ := zcolm.GetCollectionTasks(collection)\n\t\t\tif collectionTasks != nil {\n\t\t\t\tcollection.Tasks = collectionTasks\n\t\t\t}\n\t\t\t// get collection prokects, if any\n\t\t\tcollectionProjects, _ := zcolm.GetCollectionProjects(collection)\n\t\t\tif collectionProjects != nil {\n\t\t\t\tcollection.Projects = collectionProjects\n\t\t\t}\n\t\t\tallCollections = append(allCollections, collection)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a CollectionsObject with all collected tasks.\n\tcollectionsObject := CollectionsObject{\n\t\tCollections: allCollections,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the collections into JSON.\n\tmarshalledCollections, merr := collectionsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\", merr\n\t} \n\treturn string(marshalledCollections), nil\n} " + }, + { + "name": "collections_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n/*\n\nfunc Test_AddCollection(t *testing.T) {\n \n collection := Collection{Id: \"1\", RealmId: \"4\", Name: \"First collection\",}\n\n // Test adding a collection successfully.\n err := collection.AddCollection()\n if err != nil {\n t.Errorf(\"Failed to add collection: %v\", err)\n }\n\n // Test adding a duplicate task.\n cerr := collection.AddCollection()\n if cerr != ErrCollectionIdAlreadyExists {\n t.Errorf(\"Expected ErrCollectionIdAlreadyExists, got %v\", cerr)\n }\n}\n\nfunc Test_RemoveCollection(t *testing.T) {\n \n collection := Collection{Id: \"20\", RealmId: \"4\", Name: \"Removable collection\",}\n\n // Test adding a collection successfully.\n err := collection.AddCollection()\n if err != nil {\n t.Errorf(\"Failed to add collection: %v\", err)\n }\n\n retrievedCollection, rerr := GetCollectionById(collection.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added collection\")\n }\n\n // Test removing a collection\n terr := retrievedCollection.RemoveCollection()\n if terr != ErrCollectionNotRemoved {\n t.Errorf(\"Expected ErrCollectionNotRemoved, got %v\", terr)\n }\n}\n\nfunc Test_EditCollection(t *testing.T) {\n \n collection := Collection{Id: \"2\", RealmId: \"4\", Name: \"Second collection\",}\n\n // Test adding a collection successfully.\n err := collection.AddCollection()\n if err != nil {\n t.Errorf(\"Failed to add collection: %v\", err)\n }\n\n // Test editing the collection\n editedCollection := Collection{Id: collection.Id, RealmId: collection.RealmId, Name: \"Edited collection\",}\n cerr := editedCollection.EditCollection()\n if cerr != nil {\n t.Errorf(\"Failed to edit the collection\")\n }\n\n retrievedCollection, _ := GetCollectionById(editedCollection.Id)\n if retrievedCollection.Name != \"Edited collection\" {\n t.Errorf(\"Collection was not edited\")\n }\n}\n\nfunc Test_AddProjectToCollection(t *testing.T){\n // Example Collection and Projects\n col := Collection{Id: \"1\", Name: \"First collection\", RealmId: \"4\",}\n prj := Project{Id: \"10\", Body: \"Project 10\", RealmId: \"1\",}\n\n Collections.Set(col.Id, col) // Mock existing collections\n\n tests := []struct {\n name string\n collection Collection\n project Project\n wantErr bool\n errMsg error\n }{\n {\n name: \"Attach to existing collection\",\n collection: col,\n project: prj,\n wantErr: false,\n },\n {\n name: \"Attach to non-existing collection\",\n collection: Collection{Id: \"200\", Name: \"Collection 200\", RealmId: \"4\",},\n project: prj,\n wantErr: true,\n errMsg: ErrCollectionIdNotFound,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := tt.collection.AddProjectToCollection(tt.project)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"AddProjectToCollection() error = %v, wantErr %v\", err, tt.wantErr)\n }\n if tt.wantErr && err != tt.errMsg {\n t.Errorf(\"AddProjectToCollection() error = %v, expected %v\", err, tt.errMsg)\n }\n\n // For successful attach, verify the project is added to the collection's tasks.\n if !tt.wantErr {\n projects, exist := CollectionProjects.Get(tt.collection.Id)\n if !exist || len(projects.([]Project)) == 0 {\n t.Errorf(\"Project was not added to the collection\")\n } else {\n found := false\n for _, project := range projects.([]Project) {\n if project.Id == tt.project.Id {\n found = true\n break\n }\n }\n if !found {\n t.Errorf(\"Project was not attached to the collection\")\n }\n }\n }\n })\n }\n}\n\nfunc Test_AddTaskToCollection(t *testing.T){\n // Example Collection and Tasks\n col := Collection{Id: \"2\", Name: \"Second Collection\", RealmId: \"4\",}\n tsk := Task{Id: \"30\", Body: \"Task 30\", RealmId: \"1\",}\n\n Collections.Set(col.Id, col) // Mock existing collections\n\n tests := []struct {\n name string\n collection Collection\n task Task\n wantErr bool\n errMsg error\n }{\n {\n name: \"Attach to existing collection\",\n collection: col,\n task: tsk,\n wantErr: false,\n },\n {\n name: \"Attach to non-existing collection\",\n collection: Collection{Id: \"210\", Name: \"Collection 210\", RealmId: \"4\",},\n task: tsk,\n wantErr: true,\n errMsg: ErrCollectionIdNotFound,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := tt.collection.AddTaskToCollection(tt.task)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"AddTaskToCollection() error = %v, wantErr %v\", err, tt.wantErr)\n }\n if tt.wantErr && err != tt.errMsg {\n t.Errorf(\"AddTaskToCollection() error = %v, expected %v\", err, tt.errMsg)\n }\n\n // For successful attach, verify the task is added to the collection's tasks.\n if !tt.wantErr {\n tasks, exist := CollectionTasks.Get(tt.collection.Id)\n if !exist || len(tasks.([]Task)) == 0 {\n t.Errorf(\"Task was not added to the collection\")\n } else {\n found := false\n for _, task := range tasks.([]Task) {\n if task.Id == tt.task.Id {\n found = true\n break\n }\n }\n if !found {\n t.Errorf(\"Task was not attached to the collection\")\n }\n }\n }\n })\n }\n}\n\nfunc Test_RemoveProjectFromCollection(t *testing.T){\n // Setup:\n\tcollection := Collection{Id: \"300\", Name: \"Collection 300\",}\n\tproject1 := Project{Id: \"21\", Body: \"Project 21\", RealmId: \"1\",}\n\tproject2 := Project{Id: \"22\", Body: \"Project 22\", RealmId: \"1\",}\n\n collection.AddCollection()\n project1.AddProject()\n project2.AddProject()\n collection.AddProjectToCollection(project1)\n collection.AddProjectToCollection(project2)\n\n\ttests := []struct {\n\t\tname string\n\t\tproject Project\n\t\tcollection Collection\n\t\twantErr bool\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname: \"Remove existing project from collection\",\n\t\t\tproject: project1,\n\t\t\tcollection: collection,\n\t\t\twantErr: false,\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove project from non-existing collection\",\n\t\t\tproject: project1,\n\t\t\tcollection: Collection{Id: \"nonexistent\"},\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrCollectionIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove non-existing project from collection\",\n\t\t\tproject: Project{Id: \"nonexistent\"},\n\t\t\tcollection: collection,\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrProjectByIdNotFound,\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.collection.RemoveProjectFromCollection(tt.project)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil || err != tt.expectedErr {\n\t\t\t\t\tt.Errorf(\"%s: expected error %v, got %v\", tt.name, tt.expectedErr, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: unexpected error: %v\", tt.name, err)\n\t\t\t\t}\n\n\t\t\t\t// For successful removal, verify the project is no longer part of the collection's projects\n\t\t\t\tif !tt.wantErr {\n\t\t\t\t\tprojects, _ := CollectionProjects.Get(tt.collection.Id)\n\t\t\t\t\tfor _, project := range projects.([]Project) {\n\t\t\t\t\t\tif project.Id == tt.project.Id {\n\t\t\t\t\t\t\tt.Errorf(\"%s: project was not detached from the collection\", tt.name)\n\t\t\t\t\t\t\tbreak\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}\n}\n\nfunc Test_RemoveTaskFromCollection(t *testing.T){\n // setup, re-using parts from Test_AddTaskToCollection\n\tcollection := Collection{Id: \"40\", Name: \"Collection 40\",}\n task1 := Task{Id: \"40\", Body: \"Task 40\", RealmId: \"1\",}\n\n collection.AddCollection()\n task1.AddTask()\n collection.AddTaskToCollection(task1)\n\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\tcollection Collection\n\t\twantErr bool\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname: \"Remove existing task from collection\",\n\t\t\ttask: task1,\n\t\t\tcollection: collection,\n\t\t\twantErr: false,\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove task from non-existing collection\",\n\t\t\ttask: task1,\n\t\t\tcollection: Collection{Id: \"nonexistent\"},\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrCollectionIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to remove non-existing task from collection\",\n\t\t\ttask: Task{Id: \"nonexistent\"},\n\t\t\tcollection: collection,\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrTaskByIdNotFound,\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.collection.RemoveTaskFromCollection(tt.task)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil || err != tt.expectedErr {\n\t\t\t\t\tt.Errorf(\"%s: expected error %v, got %v\", tt.name, tt.expectedErr, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: unexpected error: %v\", tt.name, err)\n\t\t\t\t}\n\n\t\t\t\t// For successful removal, verify the task is no longer part of the collection's tasks\n\t\t\t\tif !tt.wantErr {\n\t\t\t\t\ttasks, _ := CollectionTasks.Get(tt.collection.Id)\n\t\t\t\t\tfor _, task := range tasks.([]Task) {\n\t\t\t\t\t\tif task.Id == tt.task.Id {\n\t\t\t\t\t\t\tt.Errorf(\"%s: task was not detached from the collection\", tt.name)\n\t\t\t\t\t\t\tbreak\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}\n}\n\nfunc Test_GetCollectionById(t *testing.T){\n // test getting a non-existing collection\n nonCollection, err := GetCollectionById(\"0\")\n if err != ErrCollectionByIdNotFound {\n t.Fatalf(\"Expected ErrCollectionByIdNotFound, got: %v\", err)\n }\n\n // test getting the correct collection by id\n correctCollection, err := GetCollectionById(\"1\")\n if err != nil {\n t.Fatalf(\"Failed to get collection by id, error: %v\", err)\n }\n\n if correctCollection.Name != \"First collection\" {\n t.Fatalf(\"Got the wrong collection, with name: %v\", correctCollection.Name)\n }\n}\n\nfunc Test_GetCollectionTasks(t *testing.T) {\n // retrieving objects based on these mocks\n //col := Collection{Id: \"2\", Name: \"Second Collection\", RealmId: \"4\",}\n tsk := Task{Id: \"30\", Body: \"Task 30\", RealmId: \"1\",}\n\n collection, cerr := GetCollectionById(\"2\")\n if cerr != nil {\n t.Errorf(\"GetCollectionById() failed, %v\", cerr)\n }\n\n collectionTasks, pterr := collection.GetCollectionTasks()\n if len(collectionTasks) == 0 {\n t.Errorf(\"GetCollectionTasks() failed, %v\", pterr)\n }\n\n // test detaching from an existing collection\n dtterr := collection.RemoveTaskFromCollection(tsk)\n if dtterr != nil {\n t.Errorf(\"RemoveTaskFromCollection() failed, %v\", dtterr)\n }\n\n collectionWithNoTasks, pterr := collection.GetCollectionTasks()\n if len(collectionWithNoTasks) != 0 {\n t.Errorf(\"GetCollectionTasks() after detach failed, %v\", pterr)\n }\n\n // add task back to collection, for tests mockup integrity\n collection.AddTaskToCollection(tsk)\n}\n\nfunc Test_GetCollectionProjects(t *testing.T) {\n // retrieving objects based on these mocks\n //col := Collection{Id: \"1\", Name: \"First Collection\", RealmId: \"4\",}\n prj := Project{Id: \"10\", Body: \"Project 10\", RealmId: \"2\", ContextId: \"2\", Due: \"2024-01-01\"}\n\n collection, cerr := GetCollectionById(\"1\")\n if cerr != nil {\n t.Errorf(\"GetCollectionById() failed, %v\", cerr)\n }\n\n collectionProjects, pterr := collection.GetCollectionProjects()\n if len(collectionProjects) == 0 {\n t.Errorf(\"GetCollectionProjects() failed, %v\", pterr)\n }\n\n // test detaching from an existing collection\n dtterr := collection.RemoveProjectFromCollection(prj)\n if dtterr != nil {\n t.Errorf(\"RemoveProjectFromCollection() failed, %v\", dtterr)\n }\n\n collectionWithNoProjects, pterr := collection.GetCollectionProjects()\n if len(collectionWithNoProjects) != 0 {\n t.Errorf(\"GetCollectionProjects() after detach failed, %v\", pterr)\n }\n\n // add project back to collection, for tests mockup integrity\n collection.AddProjectToCollection(prj)\n}\n\nfunc Test_GetAllCollections(t *testing.T){\n // mocking the collections based on previous tests\n // TODO: add isolation?\n knownCollections := []Collection{\n {\n Id: \"1\",\n RealmId: \"4\",\n Name: \"First collection\",\n Tasks: nil, \n Projects: []Project{\n {\n Id: \"10\",\n ContextId: \"2\",\n RealmId: \"4\",\n Tasks: nil, \n Body: \"Project 10\",\n Due: \"2024-01-01\",\n },\n },\n },\n {\n Id: \"2\",\n RealmId: \"4\",\n Name: \"Second Collection\",\n Tasks: []Task{\n {\n Id:\"30\",\n ProjectId:\"\",\n ContextId:\"\",\n RealmId:\"4\",\n Body:\"Task 30\",\n Due:\"\",\n Alert:\"\",\n },\n },\n Projects: nil, \n },\n {\n Id:\"20\",\n RealmId:\"4\",\n Name:\"Removable collection\",\n Tasks: nil,\n Projects: nil,\n },\n {\n Id: \"300\",\n Name: \"Collection 300\",\n Tasks: nil, \n Projects: []Project {\n {\n Id:\"22\",\n ContextId:\"\",\n RealmId:\"4\",\n Tasks: nil,\n Body:\"Project 22\",\n Due:\"\",\n },\n }, \n },\n {\n Id: \"40\",\n Name: \"Collection 40\",\n Tasks: nil, \n Projects: nil, \n },\n }\n \n\n // Manually marshal the known collections to create the expected outcome.\n collectionsObject := CollectionsObject{Collections: knownCollections}\n expected, err := collectionsObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known collections: %v\", err)\n }\n\n // Execute GetAllCollections() to get the actual outcome.\n actual, err := GetAllCollections()\n if err != nil {\n t.Fatalf(\"GetAllCollections() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual collections JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n\n\n\n\n" + }, + { + "name": "contexts.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\ntype Context struct {\n\tId string `json:\"contextId\"`\n\tName string `json:\"contextName\"`\n}\n\ntype ZContextManager struct {\n\tContexts *avl.Tree\n}\n\nfunc NewZContextManager() *ZContextManager {\n\treturn &ZContextManager{\n\t\tContexts: avl.NewTree(),\n\t}\n}\n\n// Actions\n\nfunc (zcm *ZContextManager) AddContext(c Context) error {\n\tif zcm.Contexts.Size() != 0 {\n\t\t_, exist := zcm.Contexts.Get(c.Id)\n\t\tif exist {\n\t\t\treturn ErrContextIdAlreadyExists\n\t\t}\n\t}\n\tzcm.Contexts.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) EditContext(c Context) error {\n\tif zcm.Contexts.Size() != 0 {\n\t\t_, exist := zcm.Contexts.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrContextIdNotFound\n\t\t}\n\t}\n\tzcm.Contexts.Set(c.Id, c)\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) RemoveContext(c Context) error {\n\tif zcm.Contexts.Size() != 0 {\n\t\tcontext, exist := zcm.Contexts.Get(c.Id)\n\t\tif !exist {\n\t\t\treturn ErrContextIdNotFound\n\t\t}\n\t\t_, removed := zcm.Contexts.Remove(context.(Context).Id)\n\t\tif !removed {\n\t\t\treturn ErrContextNotRemoved\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) AddContextToTask(ztm *ZTaskManager, c Context, t Task) error {\n\ttaskInterface, exist := ztm.Tasks.Get(t.Id)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\t_, cexist := zcm.Contexts.Get(c.Id)\n\tif !cexist {\n\t\treturn ErrContextIdNotFound\n\t}\n\n\tif t.RealmId == \"2\" {\n\t\ttask := taskInterface.(Task)\n\t\ttask.ContextId = c.Id\n\t\tztm.Tasks.Set(t.Id, task)\n\t} else {\n\t\treturn ErrTaskNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) AddContextToProject(zpm *ZProjectManager, c Context, p Project) error {\n\tprojectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\t_, cexist := zcm.Contexts.Get(c.Id)\n\tif !cexist {\n\t\treturn ErrContextIdNotFound\n\t}\n\n\tif p.RealmId == \"2\" {\n\t\tproject := projectInterface.(Project)\n\t\tproject.ContextId = c.Id\n\t\tzpm.Projects.Set(p.Id, project)\n\t} else {\n\t\treturn ErrProjectNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (zcm *ZContextManager) AddContextToProjectTask(zpm *ZProjectManager, c Context, p Project, projectTaskId string) error {\n\t\n\t_, cexist := zcm.Contexts.Get(c.Id)\n\tif !cexist {\n\t\treturn ErrContextIdNotFound\n\t}\n\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n\tif existingProject.RealmId != \"2\" {\n\t\treturn ErrProjectNotEditable\n\t}\n\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(p.Id)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n existingProject.Tasks = tasks\n\n var index int = -1\n for i, task := range existingProject.Tasks {\n if task.Id == projectTaskId {\n index = i\n break\n }\n }\n\n if index != -1 {\n existingProject.Tasks[index].ContextId = c.Id\n } else {\n return ErrTaskByIdNotFound\n }\n\n zpm.ProjectTasks.Set(p.Id, existingProject.Tasks)\n return nil\n}\n\n// getters\n\nfunc (zcm *ZContextManager) GetContextById(contextId string) (Context, error) {\n\tif zcm.Contexts.Size() != 0 {\n\t\tcInterface, exist := zcm.Contexts.Get(contextId)\n\t\tif exist {\n\t\t\treturn cInterface.(Context), nil\n\t\t}\n\t\treturn Context{}, ErrContextIdNotFound\n\t}\n\treturn Context{}, ErrContextIdNotFound\n}\n\nfunc (zcm *ZContextManager) GetAllContexts() (string) {\n\tvar allContexts []Context\n\n\t// Iterate over the Contexts AVL tree to collect all Context objects.\n\tzcm.Contexts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif context, ok := value.(Context); ok {\n\t\t\tallContexts = append(allContexts, context)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ContextsObject with all collected contexts.\n\tcontextsObject := &ContextsObject{\n\t\tContexts: allContexts,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the contexts into JSON.\n\tmarshalledContexts, merr := contextsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t}\n\treturn string(marshalledContexts)\n}\n\n" + }, + { + "name": "contexts_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n/*\nfunc Test_AddContext(t *testing.T) {\n \n context := Context{Id: \"1\", Name: \"Work\"}\n\n // Test adding a context successfully.\n err := context.AddContext()\n if err != nil {\n t.Errorf(\"Failed to add context: %v\", err)\n }\n\n // Test adding a duplicate task.\n cerr := context.AddContext()\n if cerr != ErrContextIdAlreadyExists {\n t.Errorf(\"Expected ErrContextIdAlreadyExists, got %v\", cerr)\n }\n}\n\nfunc Test_EditContext(t *testing.T) {\n \n context := Context{Id: \"2\", Name: \"Home\"}\n\n // Test adding a context successfully.\n err := context.AddContext()\n if err != nil {\n t.Errorf(\"Failed to add context: %v\", err)\n }\n\n // Test editing the context\n editedContext := Context{Id: \"2\", Name: \"Shopping\"}\n cerr := editedContext.EditContext()\n if cerr != nil {\n t.Errorf(\"Failed to edit the context\")\n }\n\n retrievedContext, _ := GetContextById(editedContext.Id)\n if retrievedContext.Name != \"Shopping\" {\n t.Errorf(\"Context was not edited\")\n }\n}\n\nfunc Test_RemoveContext(t *testing.T) {\n \n context := Context{Id: \"4\", Name: \"Gym\",}\n\n // Test adding a context successfully.\n err := context.AddContext()\n if err != nil {\n t.Errorf(\"Failed to add context: %v\", err)\n }\n\n retrievedContext, rerr := GetContextById(context.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added context\")\n }\n // Test removing a context\n cerr := retrievedContext.RemoveContext()\n if cerr != ErrContextNotRemoved {\n t.Errorf(\"Expected ErrContextNotRemoved, got %v\", cerr)\n }\n}\n\nfunc Test_AddContextToTask(t *testing.T) {\n\n task := Task{Id: \"10\", Body: \"First content\", RealmId: \"2\", ContextId: \"1\",}\n\n // Test adding a task successfully.\n err := task.AddTask()\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n taskInDecide, exist := Tasks.Get(\"10\")\n\tif !exist {\n\t\tt.Errorf(\"Task with id 10 not found\")\n\t}\n\t// check if context exists\n\tcontextToAdd, cexist := Contexts.Get(\"2\")\n\tif !cexist {\n\t\tt.Errorf(\"Context with id 2 not found\")\n\t}\n\n derr := contextToAdd.(Context).AddContextToTask(taskInDecide.(Task))\n if derr != nil {\n t.Errorf(\"Could not add context to a task in Decide, err %v\", derr)\n }\n}\n\nfunc Test_AddContextToProject(t *testing.T) {\n\n project := Project{Id: \"10\", Body: \"Project 10\", RealmId: \"2\", ContextId: \"1\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n projectInDecide, exist := Projects.Get(\"10\")\n\tif !exist {\n\t\tt.Errorf(\"Project with id 10 not found\")\n\t}\n\t// check if context exists\n\tcontextToAdd, cexist := Contexts.Get(\"2\")\n\tif !cexist {\n\t\tt.Errorf(\"Context with id 2 not found\")\n\t}\n\n derr := contextToAdd.(Context).AddContextToProject(projectInDecide.(Project))\n if derr != nil {\n t.Errorf(\"Could not add context to a project in Decide, err %v\", derr)\n }\n}\n\nfunc Test_GetAllContexts(t *testing.T) {\n \n // mocking the contexts based on previous tests\n // TODO: add isolation?\n knownContexts := []Context{\n {Id: \"1\", Name: \"Work\",},\n {Id: \"2\", Name: \"Shopping\",},\n {Id: \"4\", Name: \"Gym\",},\n }\n\n // Manually marshal the known contexts to create the expected outcome.\n contextsObject := ContextsObject{Contexts: knownContexts}\n expected, err := contextsObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known contexts: %v\", err)\n }\n\n // Execute GetAllContexts() to get the actual outcome.\n actual, err := GetAllContexts()\n if err != nil {\n t.Fatalf(\"GetAllContexts() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual contexts JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n\n" + }, + { + "name": "core.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// holding the path of an object since creation\n// each time we move an object from one realm to another, we add to its path\ntype ObjectPath struct {\n\tObjectType string `json:\"objectType\"` // Task, Project\n\tId string `json:\"id\"` // this is the Id of the object moved, Task, Project\n\tRealmId string `json:\"realmId\"`\n}\n\ntype ZObjectPathManager struct {\n\tPaths avl.Tree\n\tPathId int\n}\n\nfunc NewZObjectPathManager() *ZObjectPathManager {\n\treturn &ZObjectPathManager{\n\t\tPaths: *avl.NewTree(),\n\t\tPathId: 1,\n\t}\n}\n\nfunc (zopm *ZObjectPathManager) AddPath(o ObjectPath) error {\n\tzopm.PathId++\n\tupdated := zopm.Paths.Set(strconv.Itoa(zopm.PathId), o)\n\tif !updated {\n\t\treturn ErrObjectPathNotUpdated\n\t}\n\treturn nil\n}\n\nfunc (zopm *ZObjectPathManager) GetObjectJourney(objectType string, objectId string) (string, error) {\n\tvar objectPaths []ObjectPath\n\n\t// Iterate over the Paths AVL tree to collect all ObjectPath objects.\n\tzopm.Paths.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif objectPath, ok := value.(ObjectPath); ok {\n\t\t\tif objectPath.ObjectType == objectType && objectPath.Id == objectId {\n\t\t\t\tobjectPaths = append(objectPaths, objectPath)\n\t\t\t}\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create an ObjectJourney with all collected paths.\n\tobjectJourney := &ObjectJourney{\n\t\tObjectPaths: objectPaths,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the journey into JSON.\n\tmarshalledJourney, merr := objectJourney.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\", merr\n\t}\n\treturn string(marshalledJourney), nil\n}\n\n\n// GetZenStatus\n/* todo: leave it to the client\nfunc () GetZenStatus() (zenStatus string, err error) {\n\t// implementation\n}\n*/\n" + }, + { + "name": "errors.gno", + "body": "package zentasktic\n\nimport \"errors\"\n\nvar (\n\tErrTaskNotEditable \t= errors.New(\"Task is not editable\")\n\tErrProjectNotEditable = errors.New(\"Project is not editable\")\n\tErrProjectIdNotFound\t\t\t= errors.New(\"Project id not found\")\n\tErrTaskIdNotFound\t\t\t\t= errors.New(\"Task id not found\")\n\tErrTaskFailedToAssert\t\t\t= errors.New(\"Failed to assert Task type\")\n\tErrProjectFailedToAssert\t\t= errors.New(\"Failed to assert Project type\")\n\tErrProjectTasksNotFound\t\t\t= errors.New(\"Could not get tasks for project\")\n\tErrCollectionsProjectsNotFound\t= errors.New(\"Could not get projects for this collection\")\n\tErrCollectionsTasksNotFound\t\t= errors.New(\"Could not get tasks for this collection\")\n\tErrTaskIdAlreadyExists\t\t\t= errors.New(\"A task with the provided id already exists\")\n\tErrCollectionIdAlreadyExists\t= errors.New(\"A collection with the provided id already exists\")\n\tErrProjectIdAlreadyExists\t\t= errors.New(\"A project with the provided id already exists\")\n\tErrTaskByIdNotFound\t\t\t\t= errors.New(\"Can't get task by id\")\n\tErrProjectByIdNotFound\t\t\t= errors.New(\"Can't get project by id\")\n\tErrCollectionByIdNotFound\t\t= errors.New(\"Can't get collection by id\")\n\tErrTaskNotRemovable\t\t\t\t= errors.New(\"Cannot remove a task directly from this realm\")\n\tErrProjectNotRemovable\t\t\t= errors.New(\"Cannot remove a project directly from this realm\")\n\tErrProjectTasksNotRemoved\t\t= errors.New(\"Project tasks were not removed\")\n\tErrTaskNotRemoved\t\t\t\t= errors.New(\"Task was not removed\")\n\tErrTaskNotInAssessRealm\t\t\t= errors.New(\"Task is not in Assess, cannot edit Body\")\n\tErrProjectNotInAssessRealm\t\t= errors.New(\"Project is not in Assess, cannot edit Body\")\n\tErrContextIdAlreadyExists\t\t= errors.New(\"A context with the provided id already exists\")\n\tErrContextIdNotFound\t\t\t= errors.New(\"Context id not found\")\n\tErrCollectionIdNotFound\t\t\t= errors.New(\"Collection id not found\")\n\tErrContextNotRemoved\t\t\t= errors.New(\"Context was not removed\")\n\tErrProjectNotRemoved\t\t\t= errors.New(\"Project was not removed\")\n\tErrCollectionNotRemoved\t\t\t= errors.New(\"Collection was not removed\")\n\tErrObjectPathNotUpdated\t\t\t= errors.New(\"Object path wasn't updated\")\n\tErrInvalidateDateFormat\t\t\t= errors.New(\"Invalida date format\")\n\tErrInvalidDateFilterType\t\t= errors.New(\"Invalid date filter type\")\n\tErrRealmIdAlreadyExists\t\t\t= errors.New(\"A realm with the same id already exists\")\n\tErrRealmIdNotAllowed\t\t\t= errors.New(\"This is a reserved realm id\")\n\tErrRealmIdNotFound\t\t\t\t= errors.New(\"Realm id not found\")\n\tErrRealmNotRemoved\t\t\t\t= errors.New(\"Realm was not removed\")\n)" + }, + { + "name": "marshals.gno", + "body": "package zentasktic\n\nimport (\n\t\"bytes\"\n)\n\n\ntype ContextsObject struct {\n\tContexts\t[]Context\n}\n\ntype TasksObject struct {\n\tTasks\t[]Task\n}\n\ntype ProjectsObject struct {\n\tProjects\t[]Project\n}\n\ntype CollectionsObject struct {\n\tCollections\t[]Collection\n}\n\ntype RealmsObject struct {\n\tRealms\t[]Realm\n}\n\ntype ObjectJourney struct {\n\tObjectPaths []ObjectPath\n}\n\nfunc (c Context) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"contextId\":\"`)\n\tb.WriteString(c.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"contextName\":\"`)\n\tb.WriteString(c.Name)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (cs ContextsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"contexts\":[`)\n\t\n\tfor i, context := range cs.Contexts {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcontextJSON, cerr := context.MarshalJSON()\n\t\tif cerr == nil {\n\t\t\tb.WriteString(string(contextJSON))\n\t\t}\n\t}\n\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (t Task) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"taskId\":\"`)\n\tb.WriteString(t.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskProjectId\":\"`)\n\tb.WriteString(t.ProjectId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskContextId\":\"`)\n\tb.WriteString(t.ContextId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskRealmId\":\"`)\n\tb.WriteString(t.RealmId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskBody\":\"`)\n\tb.WriteString(t.Body)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskDue\":\"`)\n\tb.WriteString(t.Due)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"taskAlert\":\"`)\n\tb.WriteString(t.Alert)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (ts TasksObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"tasks\":[`)\n\tfor i, task := range ts.Tasks {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\ttaskJSON, cerr := task.MarshalJSON()\n\t\tif cerr == nil {\n\t\t\tb.WriteString(string(taskJSON))\n\t\t}\n\t}\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (p Project) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"projectId\":\"`)\n\tb.WriteString(p.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectContextId\":\"`)\n\tb.WriteString(p.ContextId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectRealmId\":\"`)\n\tb.WriteString(p.RealmId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectTasks\":[`)\n\n\tif len(p.Tasks) != 0 {\n\t\tfor i, projectTask := range p.Tasks {\n\t\t\tif i > 0 {\n\t\t\t\tb.WriteString(`,`)\n\t\t\t}\n\t\t\tprojectTaskJSON, perr := projectTask.MarshalJSON()\n\t\t\tif perr == nil {\n\t\t\t\tb.WriteString(string(projectTaskJSON))\n\t\t\t}\n\t\t}\n\t}\n\n\tb.WriteString(`],`)\n\n\tb.WriteString(`\"projectBody\":\"`)\n\tb.WriteString(p.Body)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"projectDue\":\"`)\n\tb.WriteString(p.Due)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (ps ProjectsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"projects\":[`)\n\tfor i, project := range ps.Projects {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tprojectJSON, perr := project.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(projectJSON))\n\t\t}\n\t}\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (c Collection) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"collectionId\":\"`)\n\tb.WriteString(c.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"collectionRealmId\":\"`)\n\tb.WriteString(c.RealmId)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"collectionName\":\"`)\n\tb.WriteString(c.Name)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"collectionTasks\":[`)\n\tfor i, collectionTask := range c.Tasks {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcollectionTaskJSON, perr := collectionTask.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(collectionTaskJSON))\n\t\t}\n\t}\n\tb.WriteString(`],`)\n\n\tb.WriteString(`\"collectionProjects\":[`)\n\tfor i, collectionProject := range c.Projects {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcollectionProjectJSON, perr := collectionProject.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(collectionProjectJSON))\n\t\t}\n\t}\n\tb.WriteString(`],`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (co CollectionsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"collections\":[`)\n\tfor i, collection := range co.Collections {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tcollectionJSON, perr := collection.MarshalJSON()\n\t\tif perr == nil {\n\t\t\tb.WriteString(string(collectionJSON))\n\t\t}\n\t}\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (r Realm) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"realmId\":\"`)\n\tb.WriteString(r.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"realmName\":\"`)\n\tb.WriteString(r.Name)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (rs RealmsObject) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"realms\":[`)\n\t\n\tfor i, realm := range rs.Realms {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\trealmJSON, rerr := realm.MarshalJSON()\n\t\tif rerr == nil {\n\t\t\tb.WriteString(string(realmJSON))\n\t\t}\n\t}\n\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n\nfunc (op ObjectPath) MarshalJSON() ([]byte, error) {\n\n\tvar b bytes.Buffer\n\t\n\tb.WriteByte('{')\n\n\tb.WriteString(`\"objectType\":\"`)\n\tb.WriteString(op.ObjectType)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"id\":\"`)\n\tb.WriteString(op.Id)\n\tb.WriteString(`\",`)\n\n\tb.WriteString(`\"realmId\":\"`)\n\tb.WriteString(op.RealmId)\n\tb.WriteString(`\"`)\n\n\tb.WriteByte('}')\n\n\treturn b.Bytes(), nil\n}\n\nfunc (oj ObjectJourney) MarshalJSON() ([]byte, error) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(`{\"objectJourney\":[`)\n\t\n\tfor i, objectPath := range oj.ObjectPaths {\n\t\tif i > 0 {\n\t\t\tb.WriteString(`,`)\n\t\t}\n\t\tobjectPathJSON, oerr := objectPath.MarshalJSON()\n\t\tif oerr == nil {\n\t\t\tb.WriteString(string(objectPathJSON))\n\t\t}\n\t}\n\n\tb.WriteString(`]}`)\n\n\treturn b.Bytes(), nil\n}\n" + }, + { + "name": "projects.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n\ntype Project struct {\n\tId \t\t\tstring `json:\"projectId\"`\n\tContextId\tstring `json:\"projectContextId\"`\n\tRealmId \tstring `json:\"projectRealmId\"`\n\tTasks\t\t[]Task `json:\"projectTasks\"`\n\tBody \t\tstring `json:\"projectBody\"`\n\tDue\t\t\tstring `json:\"projectDue\"`\n}\n\ntype ZProjectManager struct {\n\tProjects *avl.Tree // projectId -> Project\n\tProjectTasks *avl.Tree // projectId -> []Task\n}\n\n\nfunc NewZProjectManager() *ZProjectManager {\n\treturn &ZProjectManager{\n\t\tProjects: avl.NewTree(),\n\t\tProjectTasks: avl.NewTree(),\n\t}\n}\n\n// actions\n\nfunc (zpm *ZProjectManager) AddProject(p Project) (err error) {\n\t// implementation\n\n\tif zpm.Projects.Size() != 0 {\n\t\t_, exist := zpm.Projects.Get(p.Id)\n\t\tif exist {\n\t\t\treturn ErrProjectIdAlreadyExists\n\t\t}\n\t}\n\tzpm.Projects.Set(p.Id, p)\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) RemoveProject(p Project) (err error) {\n\t// implementation, remove from ProjectTasks too\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\t // project is removable only in Asses (RealmId 1) or via a Collection (RealmId 4)\n\tif existingProject.RealmId != \"1\" && existingProject.RealmId != \"4\" {\n\t\treturn ErrProjectNotRemovable\n\t}\n\n\t_, removed := zpm.Projects.Remove(existingProject.Id)\n\tif !removed {\n\t\treturn ErrProjectNotRemoved\n\t}\n\n\t// manage project tasks, if any\n\n\tif zpm.ProjectTasks.Size() != 0 {\n\t\t_, exist := zpm.ProjectTasks.Get(existingProject.Id)\n\t\tif !exist {\n\t\t\t// if there's no record in ProjectTasks, we don't have to remove anything\n\t\t\treturn nil\n\t\t} else {\n\t\t\t_, removed := zpm.ProjectTasks.Remove(existingProject.Id)\n\t\t\tif !removed {\n\t\t\t\treturn ErrProjectTasksNotRemoved\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) EditProject(p Project) (err error) {\n\t// implementation, get project by Id and replace the object\n\t// this is for the project body and realm, project tasks are managed in the Tasks object\n\texistingProject := Project{}\n\tif zpm.Projects.Size() != 0 {\n\t\t_, exist := zpm.Projects.Get(p.Id)\n\t\tif !exist {\n\t\t\treturn ErrProjectIdNotFound\n\t\t}\n\t}\n\t\n\t// project Body is editable only when project is in Assess, RealmId = \"1\"\n\tif p.RealmId != \"1\" {\n\t\tif p.Body != existingProject.Body {\n\t\t\treturn ErrProjectNotInAssessRealm\n\t\t}\n\t}\n\n\tzpm.Projects.Set(p.Id, p)\n\treturn nil\n}\n\n// helper function, we can achieve the same with EditProject() above\n/*func (zpm *ZProjectManager) MoveProjectToRealm(projectId string, realmId string) (err error) {\n\t// implementation\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\texistingProject.RealmId = realmId\n\tzpm.Projects.Set(projectId, existingProject)\n\treturn nil\n}*/\n\nfunc (zpm *ZProjectManager) MoveProjectToRealm(projectId string, realmId string) error {\n\t// Get the existing project from the Projects map\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\t// Set the project's RealmId to the new RealmId\n\texistingProject.RealmId = realmId\n\n\t// Get the existing project tasks from the ProjectTasks map\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n\tif !texist {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\ttasks, ok := existingProjectTasksInterface.([]Task)\n\tif !ok {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\n\t// Iterate through the project's tasks and set their RealmId to the new RealmId\n\tfor i := range tasks {\n\t\ttasks[i].RealmId = realmId\n\t}\n\n\t// Set the updated tasks back into the ProjectTasks map\n\tzpm.ProjectTasks.Set(projectId, tasks)\n\n\t// Set the updated project back into the Projects map\n\tzpm.Projects.Set(projectId, existingProject)\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) MarkProjectTaskAsDone(projectId string, projectTaskId string) error {\n // Get the existing project from the Projects map\n existingProjectInterface, exist := zpm.Projects.Get(projectId)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n // Get the existing project tasks from the ProjectTasks map\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n\n // Iterate through the project's tasks to find the task to be updated\n var taskFound bool\n for i, task := range tasks {\n if task.Id == projectTaskId {\n tasks[i].RealmId = \"4\" // Change the RealmId to \"4\"\n taskFound = true\n break\n }\n }\n\n if !taskFound {\n return ErrTaskByIdNotFound\n }\n\n // Set the updated tasks back into the ProjectTasks map\n zpm.ProjectTasks.Set(existingProject.Id, tasks)\n\n return nil\n}\n\n\nfunc (zpm *ZProjectManager) GetProjectTasks(p Project) (tasks []Task, err error) {\n\t// implementation, query ProjectTasks and return the []Tasks object\n\tvar existingProjectTasks []Task\n\n\tif zpm.ProjectTasks.Size() != 0 {\n\t\tprojectTasksInterface, exist := zpm.ProjectTasks.Get(p.Id)\n\t\tif !exist {\n\t\t\treturn nil, ErrProjectTasksNotFound\n\t\t}\n\t\texistingProjectTasks = projectTasksInterface.([]Task)\n\t\treturn existingProjectTasks, nil\n\t}\n\treturn nil, nil\n}\n\nfunc (zpm *ZProjectManager) SetProjectDueDate(projectId string, dueDate string) (err error) {\n\tprojectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\tproject := projectInterface.(Project)\n\n\t// check to see if project is in RealmId = 2 (Decide)\n\tif project.RealmId == \"2\" {\n\t\tproject.Due = dueDate\n\t\tzpm.Projects.Set(project.Id, project)\n\t} else {\n\t\treturn ErrProjectNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) SetProjectTaskDueDate(projectId string, projectTaskId string, dueDate string) (err error){\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n\tif existingProject.RealmId != \"2\" {\n\t\treturn ErrProjectNotEditable\n\t}\n\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n existingProject.Tasks = tasks\n\n var index int = -1\n for i, task := range existingProject.Tasks {\n if task.Id == projectTaskId {\n index = i\n break\n }\n }\n\n if index != -1 {\n existingProject.Tasks[index].Due = dueDate\n } else {\n return ErrTaskByIdNotFound\n }\n\n zpm.ProjectTasks.Set(projectId, existingProject.Tasks)\n return nil\n}\n\n// getters\n\nfunc (zpm *ZProjectManager) GetProjectById(projectId string) (Project, error) {\n\tif zpm.Projects.Size() != 0 {\n\t\tpInterface, exist := zpm.Projects.Get(projectId)\n\t\tif exist {\n\t\t\treturn pInterface.(Project), nil\n\t\t}\n\t}\n\treturn Project{}, ErrProjectIdNotFound\n}\n\nfunc (zpm *ZProjectManager) GetAllProjects() (projects string) {\n\t// implementation\n\tvar allProjects []Project\n\t\n\t// Iterate over the Projects AVL tree to collect all Project objects.\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif project, ok := value.(Project); ok {\n\t\t\t// get project tasks, if any\n\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\tif projectTasks != nil {\n\t\t\t\tproject.Tasks = projectTasks\n\t\t\t}\n\t\t\tallProjects = append(allProjects, project)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: allProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n}\n\nfunc (zpm *ZProjectManager) GetProjectsByRealm(realmId string) (projects string) {\n\t// implementation\n\tvar realmProjects []Project\n\t\n\t// Iterate over the Projects AVL tree to collect all Project objects.\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif project, ok := value.(Project); ok {\n\t\t\tif project.RealmId == realmId {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\trealmProjects = append(realmProjects, project)\n\t\t\t}\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: realmProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n}\n\nfunc (zpm *ZProjectManager) GetProjectsByContextAndRealm(contextId string, realmId string) (projects string) {\n\t// implementation\n\tvar contextProjects []Project\n\t\n\t// Iterate over the Projects AVL tree to collect all Project objects.\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif project, ok := value.(Project); ok {\n\t\t\tif project.ContextId == contextId && project.RealmId == realmId {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tcontextProjects = append(contextProjects, project)\n\t\t\t}\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: contextProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n}\n\nfunc (zpm *ZProjectManager) GetProjectsByDate(projectDate string, filterType string) (projects string) {\n\t// implementation\n\tparsedDate, err:= time.Parse(\"2006-01-02\", projectDate)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tvar filteredProjects []Project\n\t\n\tzpm.Projects.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tproject, ok := value.(Project)\n\t\tif !ok {\n\t\t\treturn false // Skip this iteration and continue.\n\t\t}\n\n\t\tstoredDate, serr := time.Parse(\"2006-01-02\", project.Due)\n\t\tif serr != nil {\n\t\t\t// Skip projects with invalid dates.\n\t\t\treturn false\n\t\t}\n\n\t\tswitch filterType {\n\t\tcase \"specific\":\n\t\t\tif storedDate.Format(\"2006-01-02\") == parsedDate.Format(\"2006-01-02\") {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tfilteredProjects = append(filteredProjects, project)\n\t\t\t}\n\t\tcase \"before\":\n\t\t\tif storedDate.Before(parsedDate) {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tfilteredProjects = append(filteredProjects, project)\n\t\t\t}\n\t\tcase \"after\":\n\t\t\tif storedDate.After(parsedDate) {\n\t\t\t\t// get project tasks, if any\n\t\t\t\tprojectTasks, _ := zpm.GetProjectTasks(project)\n\t\t\t\tif projectTasks != nil {\n\t\t\t\t\tproject.Tasks = projectTasks\n\t\t\t\t}\n\t\t\t\tfilteredProjects = append(filteredProjects, project)\n\t\t\t}\n\t\t}\n\n\t\treturn false // Continue iteration.\n\t})\n\n\tif len(filteredProjects) == 0 {\n\t\treturn \"\"\n\t}\n\n\t// Create a ProjectsObject with all collected tasks.\n\tprojectsObject := ProjectsObject{\n\t\tProjects: filteredProjects,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledProjects, merr := projectsObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledProjects)\n\n}\n" + }, + { + "name": "projects_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n/*\nfunc Test_AddProject(t *testing.T) {\n \n project := Project{Id: \"1\", RealmId: \"1\", Body: \"First project\", ContextId: \"1\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n // Test adding a duplicate project.\n cerr := project.AddProject()\n if cerr != ErrProjectIdAlreadyExists {\n t.Errorf(\"Expected ErrProjectIdAlreadyExists, got %v\", cerr)\n }\n}\n\n\nfunc Test_RemoveProject(t *testing.T) {\n \n project := Project{Id: \"20\", Body: \"Removable project\", RealmId: \"1\", ContextId: \"2\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n retrievedProject, rerr := GetProjectById(project.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added project\")\n }\n\n // Test removing a project\n terr := retrievedProject.RemoveProject()\n if terr != ErrProjectNotRemoved {\n t.Errorf(\"Expected ErrProjectNotRemoved, got %v\", terr)\n }\n}\n\n\nfunc Test_EditProject(t *testing.T) {\n \n project := Project{Id: \"2\", Body: \"Second project content\", RealmId: \"1\", ContextId: \"2\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n // Test editing the project\n editedProject := Project{Id: project.Id, Body: \"Edited project content\", RealmId: project.RealmId, ContextId: \"2\",}\n cerr := editedProject.EditProject()\n if cerr != nil {\n t.Errorf(\"Failed to edit the project\")\n }\n\n retrievedProject, _ := GetProjectById(editedProject.Id)\n if retrievedProject.Body != \"Edited project content\" {\n t.Errorf(\"Project was not edited\")\n }\n}\n\n\nfunc Test_MoveProjectToRealm(t *testing.T) {\n \n project := Project{Id: \"3\", Body: \"Project id 3 content\", RealmId: \"1\", ContextId: \"1\",}\n\n // Test adding a project successfully.\n err := project.AddProject()\n if err != nil {\n t.Errorf(\"Failed to add project: %v\", err)\n }\n\n // Test moving the project to another realm\n \n cerr := project.MoveProjectToRealm(\"2\")\n if cerr != nil {\n t.Errorf(\"Failed to move project to another realm\")\n }\n\n retrievedProject, _ := GetProjectById(project.Id)\n if retrievedProject.RealmId != \"2\" {\n t.Errorf(\"Project was moved to the wrong realm\")\n }\n}\n\nfunc Test_SetProjectDueDate(t *testing.T) {\n\tprojectRealmIdOne, _ := GetProjectById(\"1\")\n projectRealmIdTwo, _ := GetProjectById(\"10\") \n\t// Define test cases\n\ttests := []struct {\n\t\tname string\n\t\tproject Project\n\t\tdueDate string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"Project does not exist\",\n\t\t\tproject: Project{Id: \"nonexistent\", RealmId: \"2\"},\n\t\t\twantErr: ErrProjectIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Project not editable due to wrong realm\",\n\t\t\tproject: projectRealmIdOne,\n\t\t\twantErr: ErrProjectNotEditable,\n\t\t},\n\t\t{\n\t\t\tname: \"Successfully set alert\",\n\t\t\tproject: projectRealmIdTwo,\n\t\t\tdueDate: \"2024-01-01\",\n\t\t\twantErr: nil,\n\t\t},\n\t}\n\n\t// Execute test cases\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := tc.project.SetProjectDueDate(tc.dueDate)\n\n\t\t\t// Validate\n\t\t\tif err != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error %v, got %v\", tc.wantErr, err)\n\t\t\t}\n\n\t\t\t// Additional check for the success case to ensure the due date was actually set\n\t\t\tif err == nil {\n\t\t\t\t// Fetch the task again to check if the due date was set correctly\n\t\t\t\tupdatedProject, exist := Projects.Get(tc.project.Id)\n\t\t\t\tif !exist {\n\t\t\t\t\tt.Fatalf(\"Project %v was not found after setting the due date\", tc.project.Id)\n\t\t\t\t}\n\t\t\t\tif updatedProject.(Project).Due != tc.dueDate {\n\t\t\t\t\tt.Errorf(\"Expected due date to be %v, got %v\", tc.dueDate, updatedProject.(Project).Due)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// getters\n\nfunc Test_GetAllProjects(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n knownProjects := []Project{\n {Id: \"1\", Body: \"First project\", RealmId: \"1\", ContextId: \"1\",},\n {Id: \"10\", Body: \"Project 10\", RealmId: \"2\", ContextId: \"2\", Due: \"2024-01-01\"},\n\t\t{Id: \"2\", Body: \"Edited project content\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable project\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"21\", Body: \"Project 21\", RealmId: \"1\",},\n {Id: \"22\", Body: \"Project 22\", RealmId: \"1\",},\n\t\t{Id: \"3\", Body: \"Project id 3 content\", RealmId: \"2\", ContextId: \"1\",},\n }\n\n // Manually marshal the known projects to create the expected outcome.\n projectsObject := ProjectsObject{Projects: knownProjects}\n expected, err := projectsObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known projects: %v\", err)\n }\n\n // Execute GetAllProjects() to get the actual outcome.\n actual, err := GetAllProjects()\n if err != nil {\n t.Fatalf(\"GetAllProjects() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual project JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetProjectsByDate(t *testing.T) {\n\t\n\ttests := []struct {\n\t\tname string\n\t\tprojectDate string\n\t\tfilterType string\n\t\twant string\n\t\twantErr bool\n\t}{\n\t\t{\"SpecificDate\", \"2024-01-01\", \"specific\", `{\"projects\":[{\"projectId\":\"10\",\"projectContextId\":\"2\",\"projectRealmId\":\"2\",\"projectTasks\":[],\"projectBody\":\"Project 10\",\"projectDue\":\"2024-01-01\"}]}`, false},\n\t\t{\"BeforeDate\", \"2022-04-05\", \"before\", \"\", false},\n\t\t{\"AfterDate\", \"2025-04-05\", \"after\", \"\", false},\n\t\t{\"NoMatch\", \"2002-04-07\", \"specific\", \"\", false},\n\t\t{\"InvalidDateFormat\", \"April 5, 2023\", \"specific\", \"\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := GetProjectsByDate(tt.projectDate, tt.filterType)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetProjectsByDate() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && got != tt.want {\n\t\t\t\tt.Errorf(\"GetProjectsByDate() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_GetProjectTasks(t *testing.T){\n \n task := Task{Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",}\n\n project, perr := GetProjectById(\"1\")\n if perr != nil {\n t.Errorf(\"GetProjectById() failed, %v\", perr)\n }\n\n // test attaching to an existing project\n atterr := task.AttachTaskToProject(project)\n if atterr != nil {\n t.Errorf(\"AttachTaskToProject() failed, %v\", atterr)\n }\n\n projectTasks, pterr := project.GetProjectTasks()\n if len(projectTasks) == 0 {\n t.Errorf(\"GetProjectTasks() failed, %v\", pterr)\n }\n\n // test detaching from an existing project\n dtterr := task.DetachTaskFromProject(project)\n if dtterr != nil {\n t.Errorf(\"DetachTaskFromProject() failed, %v\", dtterr)\n }\n\n projectWithNoTasks, pterr := project.GetProjectTasks()\n if len(projectWithNoTasks) != 0 {\n t.Errorf(\"GetProjectTasks() after detach failed, %v\", pterr)\n }\n}\n\nfunc Test_GetProjectById(t *testing.T){\n // test getting a non-existing project\n nonProject, err := GetProjectById(\"0\")\n if err != ErrProjectByIdNotFound {\n t.Fatalf(\"Expected ErrProjectByIdNotFound, got: %v\", err)\n }\n\n // test getting the correct task by id\n correctProject, err := GetProjectById(\"1\")\n if err != nil {\n t.Fatalf(\"Failed to get project by id, error: %v\", err)\n }\n\n if correctProject.Body != \"First project\" {\n t.Fatalf(\"Got the wrong project, with body: %v\", correctProject.Body)\n }\n}\n\nfunc Test_GetProjectsByRealm(t *testing.T) {\n \n // mocking the projects based on previous tests\n // TODO: add isolation?\n projectsInAssessRealm := []Project{\n {Id: \"1\", Body: \"First project\", RealmId: \"1\", ContextId: \"1\",},\n\t\t{Id: \"2\", Body: \"Edited project content\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable project\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"21\", Body: \"Project 21\", RealmId: \"1\",},\n {Id: \"22\", Body: \"Project 22\", RealmId: \"1\",},\n }\n\n // Manually marshal the known projects to create the expected outcome.\n projectsObjectAssess := ProjectsObject{Projects: projectsInAssessRealm}\n expected, err := projectsObjectAssess.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal projects in Assess: %v\", err)\n }\n\n actual, err := GetProjectsByRealm(\"1\")\n if err != nil {\n t.Fatalf(\"GetProjectByRealm('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual projects JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetProjectsByContext(t *testing.T) {\n \n // mocking the projects based on previous tests\n // TODO: add isolation?\n projectsInContextOne := []Project{\n {Id: \"1\", Body: \"First project\", RealmId: \"1\", ContextId: \"1\",},\n\t\t{Id: \"3\", Body: \"Project id 3 content\", RealmId: \"2\", ContextId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n projectsObjectForContexts := ProjectsObject{Projects: projectsInContextOne}\n expected, err := projectsObjectForContexts.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal projects for ContextId 1: %v\", err)\n }\n\n actual, err := GetProjectsByContext(\"1\")\n if err != nil {\n t.Fatalf(\"GetProjectsByContext('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual project JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n\n" + }, + { + "name": "realms.gno", + "body": "// base implementation\npackage zentasktic\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\n// structs\n\ntype Realm struct {\n\tId \t\t\tstring `json:\"realmId\"`\n\tName \t\tstring `json:\"realmName\"`\n}\n\ntype ZRealmManager struct {\n\tRealms *avl.Tree\n}\n\nfunc NewZRealmManager() *ZRealmManager {\n\tzrm := &ZRealmManager{\n\t\tRealms: avl.NewTree(),\n\t}\n\tzrm.initializeHardcodedRealms()\n\treturn zrm\n}\n\n\nfunc (zrm *ZRealmManager) initializeHardcodedRealms() {\n\thardcodedRealms := []Realm{\n\t\t{Id: \"1\", Name: \"Assess\"},\n\t\t{Id: \"2\", Name: \"Decide\"},\n\t\t{Id: \"3\", Name: \"Do\"},\n\t\t{Id: \"4\", Name: \"Collections\"},\n\t}\n\n\tfor _, realm := range hardcodedRealms {\n\t\tzrm.Realms.Set(realm.Id, realm)\n\t}\n}\n\n\nfunc (zrm *ZRealmManager) AddRealm(r Realm) (err error){\n\t// implementation\n\tif zrm.Realms.Size() != 0 {\n\t\t_, exist := zrm.Realms.Get(r.Id)\n\t\tif exist {\n\t\t\treturn ErrRealmIdAlreadyExists\n\t\t}\n\t}\n\t// check for hardcoded values\n\tif r.Id == \"1\" || r.Id == \"2\" || r.Id == \"3\" || r.Id == \"4\" {\n\t\treturn ErrRealmIdNotAllowed\n\t}\n\tzrm.Realms.Set(r.Id, r)\n\treturn nil\n\t\n}\n\nfunc (zrm *ZRealmManager) RemoveRealm(r Realm) (err error){\n\t// implementation\n\tif zrm.Realms.Size() != 0 {\n\t\t_, exist := zrm.Realms.Get(r.Id)\n\t\tif !exist {\n\t\t\treturn ErrRealmIdNotFound\n\t\t} else {\n\t\t\t// check for hardcoded values, not removable\n\t\t\tif r.Id == \"1\" || r.Id == \"2\" || r.Id == \"3\" || r.Id == \"4\" {\n\t\t\t\treturn ErrRealmIdNotAllowed\n\t\t\t}\n\t\t}\n\t}\n\t\n\t_, removed := zrm.Realms.Remove(r.Id)\n\tif !removed {\n\t\treturn ErrRealmNotRemoved\n\t}\n\treturn nil\n\t\n}\n\n// getters\nfunc (zrm *ZRealmManager) GetRealmById(realmId string) (r Realm, err error) {\n\t// implementation\n\tif zrm.Realms.Size() != 0 {\n\t\trInterface, exist := zrm.Realms.Get(realmId)\n\t\tif exist {\n\t\t\treturn rInterface.(Realm), nil\n\t\t} else {\n\t\t\treturn Realm{}, ErrRealmIdNotFound\n\t\t}\n\t}\n\treturn Realm{}, ErrRealmIdNotFound\n}\n\nfunc (zrm *ZRealmManager) GetRealms() (realms string, err error) {\n\t// implementation\n\tvar allRealms []Realm\n\n\t// Iterate over the Realms AVL tree to collect all Context objects.\n\tzrm.Realms.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif realm, ok := value.(Realm); ok {\n\t\t\tallRealms = append(allRealms, realm)\n\t\t}\n\t\treturn false // Continue iteration until all nodes have been visited.\n\t})\n\n\n\t// Create a RealmsObject with all collected contexts.\n\trealmsObject := &RealmsObject{\n\t\tRealms: allRealms,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the realms into JSON.\n\tmarshalledRealms, rerr := realmsObject.MarshalJSON()\n\tif rerr != nil {\n\t\treturn \"\", rerr\n\t} \n\treturn string(marshalledRealms), nil\n}\n" + }, + { + "name": "tasks.gno", + "body": "package zentasktic\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype Task struct {\n\tId \t\t\tstring `json:\"taskId\"`\n\tProjectId \tstring `json:\"taskProjectId\"`\n\tContextId\tstring `json:\"taskContextId\"`\n\tRealmId \tstring `json:\"taskRealmId\"`\n\tBody \t\tstring `json:\"taskBody\"`\n\tDue\t\t\tstring `json:\"taskDue\"`\n\tAlert\t\tstring `json:\"taskAlert\"`\n}\n\ntype ZTaskManager struct {\n\tTasks *avl.Tree\n}\n\nfunc NewZTaskManager() *ZTaskManager {\n\treturn &ZTaskManager{\n\t\tTasks: avl.NewTree(),\n\t}\n}\n\n// actions\n\nfunc (ztm *ZTaskManager) AddTask(t Task) error {\n\tif ztm.Tasks.Size() != 0 {\n\t\t_, exist := ztm.Tasks.Get(t.Id)\n\t\tif exist {\n\t\t\treturn ErrTaskIdAlreadyExists\n\t\t}\n\t}\n\tztm.Tasks.Set(t.Id, t)\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) RemoveTask(t Task) error {\n\texistingTaskInterface, exist := ztm.Tasks.Get(t.Id)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\texistingTask := existingTaskInterface.(Task)\n\n\t // task is removable only in Asses (RealmId 1) or via a Collection (RealmId 4)\n\tif existingTask.RealmId != \"1\" && existingTask.RealmId != \"4\" {\n\t\treturn ErrTaskNotRemovable\n\t}\n\n\t_, removed := ztm.Tasks.Remove(existingTask.Id)\n\tif !removed {\n\t\treturn ErrTaskNotRemoved\n\t}\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) EditTask(t Task) error {\n\texistingTaskInterface, exist := ztm.Tasks.Get(t.Id)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\texistingTask := existingTaskInterface.(Task)\n\n\t// task Body is editable only when task is in Assess, RealmId = \"1\"\n\tif t.RealmId != \"1\" {\n\t\tif t.Body != existingTask.Body {\n\t\t\treturn ErrTaskNotInAssessRealm\n\t\t}\n\t}\n\n\tztm.Tasks.Set(t.Id, t)\n\treturn nil\n}\n\n// Helper function to move a task to a different realm\nfunc (ztm *ZTaskManager) MoveTaskToRealm(taskId, realmId string) error {\n\texistingTaskInterface, exist := ztm.Tasks.Get(taskId)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\texistingTask := existingTaskInterface.(Task)\n\texistingTask.RealmId = realmId\n\tztm.Tasks.Set(taskId, existingTask)\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) SetTaskDueDate(taskId, dueDate string) error {\n\ttaskInterface, exist := ztm.Tasks.Get(taskId)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\ttask := taskInterface.(Task)\n\n\tif task.RealmId == \"2\" {\n\t\ttask.Due = dueDate\n\t\tztm.Tasks.Set(task.Id, task)\n\t} else {\n\t\treturn ErrTaskNotEditable\n\t}\n\n\treturn nil\n}\n\nfunc (ztm *ZTaskManager) SetTaskAlert(taskId, alertDate string) error {\n\ttaskInterface, exist := ztm.Tasks.Get(taskId)\n\tif !exist {\n\t\treturn ErrTaskIdNotFound\n\t}\n\ttask := taskInterface.(Task)\n\n\tif task.RealmId == \"2\" {\n\t\ttask.Alert = alertDate\n\t\tztm.Tasks.Set(task.Id, task)\n\t} else {\n\t\treturn ErrTaskNotEditable\n\t}\n\n\treturn nil\n}\n\n// tasks & projects association\n\nfunc (zpm *ZProjectManager) AttachTaskToProject(ztm *ZTaskManager, t Task, p Project) error {\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(p.Id)\n\tif !texist {\n\t\texistingProject.Tasks = []Task{}\n\t} else {\n\t\ttasks, ok := existingProjectTasksInterface.([]Task)\n\t\tif !ok {\n\t\t\treturn ErrProjectTasksNotFound\n\t\t}\n\t\texistingProject.Tasks = tasks\n\t}\n\n\tt.ProjectId = p.Id\n\t// @todo we need to remove it from Tasks if it was previously added there, then detached\n\texistingTask, err := ztm.GetTaskById(t.Id)\n\tif err == nil {\n\t\tztm.RemoveTask(existingTask)\n\t}\n\tupdatedTasks := append(existingProject.Tasks, t)\n\tzpm.ProjectTasks.Set(p.Id, updatedTasks)\n\n\treturn nil\n}\n\nfunc (zpm *ZProjectManager) EditProjectTask(projectTaskId string, projectTaskBody string, projectId string) error {\n existingProjectInterface, exist := zpm.Projects.Get(projectId)\n if !exist {\n return ErrProjectIdNotFound\n }\n existingProject := existingProjectInterface.(Project)\n\n\tif existingProject.RealmId != \"1\" {\n\t\treturn ErrProjectNotEditable\n\t}\n\n existingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n if !texist {\n return ErrProjectTasksNotFound\n }\n tasks, ok := existingProjectTasksInterface.([]Task)\n if !ok {\n return ErrProjectTasksNotFound\n }\n existingProject.Tasks = tasks\n\n var index int = -1\n for i, task := range existingProject.Tasks {\n if task.Id == projectTaskId {\n index = i\n break\n }\n }\n\n if index != -1 {\n existingProject.Tasks[index].Body = projectTaskBody\n } else {\n return ErrTaskByIdNotFound\n }\n\n zpm.ProjectTasks.Set(projectId, existingProject.Tasks)\n return nil\n}\n\nfunc (zpm *ZProjectManager) DetachTaskFromProject(ztm *ZTaskManager, projectTaskId string, detachedTaskId string, p Project) error {\n\texistingProjectInterface, exist := zpm.Projects.Get(p.Id)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(p.Id)\n\tif !texist {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\ttasks, ok := existingProjectTasksInterface.([]Task)\n\tif !ok {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\texistingProject.Tasks = tasks\n\n\tvar foundTask Task\n\tvar index int = -1\n\tfor i, task := range existingProject.Tasks {\n\t\tif task.Id == projectTaskId {\n\t\t\tindex = i\n\t\t\tfoundTask = task\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != -1 {\n\t\texistingProject.Tasks = append(existingProject.Tasks[:index], existingProject.Tasks[index+1:]...)\n\t} else {\n\t\treturn ErrTaskByIdNotFound\n\t}\n\n\tfoundTask.ProjectId = \"\"\n\tfoundTask.Id = detachedTaskId\n\t// Tasks and ProjectTasks have different storage, if a task is detached from a Project\n\t// we add it to the Tasks storage\n\tif err := ztm.AddTask(foundTask); err != nil {\n\t\treturn err\n\t}\n\n\tzpm.ProjectTasks.Set(p.Id, existingProject.Tasks)\n\treturn nil\n}\n\n\nfunc (zpm *ZProjectManager) RemoveTaskFromProject(projectTaskId string, projectId string) error {\n\texistingProjectInterface, exist := zpm.Projects.Get(projectId)\n\tif !exist {\n\t\treturn ErrProjectIdNotFound\n\t}\n\texistingProject := existingProjectInterface.(Project)\n\n\texistingProjectTasksInterface, texist := zpm.ProjectTasks.Get(projectId)\n\tif !texist {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\ttasks, ok := existingProjectTasksInterface.([]Task)\n\tif !ok {\n\t\treturn ErrProjectTasksNotFound\n\t}\n\texistingProject.Tasks = tasks\n\n\tvar index int = -1\n\tfor i, task := range existingProject.Tasks {\n\t\tif task.Id == projectTaskId {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != -1 {\n\t\texistingProject.Tasks = append(existingProject.Tasks[:index], existingProject.Tasks[index+1:]...)\n\t} else {\n\t\treturn ErrTaskByIdNotFound\n\t}\n\n\tzpm.ProjectTasks.Set(projectId, existingProject.Tasks)\n\treturn nil\n}\n\n// getters\n\nfunc (ztm *ZTaskManager) GetTaskById(taskId string) (Task, error) {\n\tif ztm.Tasks.Size() != 0 {\n\t\ttInterface, exist := ztm.Tasks.Get(taskId)\n\t\tif exist {\n\t\t\treturn tInterface.(Task), nil\n\t\t}\n\t}\n\treturn Task{}, ErrTaskIdNotFound\n}\n\nfunc (ztm *ZTaskManager) GetAllTasks() (task string) {\n\tvar allTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif task, ok := value.(Task); ok {\n\t\t\tallTasks = append(allTasks, task)\n\t\t}\n\t\treturn false\n\t})\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: allTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\t\n}\n\nfunc (ztm *ZTaskManager) GetTasksByRealm(realmId string) (tasks string) {\n\tvar realmTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif task, ok := value.(Task); ok {\n\t\t\tif task.RealmId == realmId {\n\t\t\t\trealmTasks = append(realmTasks, task)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: realmTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\n}\n\nfunc (ztm *ZTaskManager) GetTasksByContextAndRealm(contextId string, realmId string) (tasks string) {\n\tvar contextTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tif task, ok := value.(Task); ok {\n\t\t\tif task.ContextId == contextId && task.ContextId == realmId {\n\t\t\t\tcontextTasks = append(contextTasks, task)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: contextTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\n}\n\nfunc (ztm *ZTaskManager) GetTasksByDate(taskDate string, filterType string) (tasks string) {\n\tparsedDate, err := time.Parse(\"2006-01-02\", taskDate)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tvar filteredTasks []Task\n\n\tztm.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttask, ok := value.(Task)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tstoredDate, serr := time.Parse(\"2006-01-02\", task.Due)\n\t\tif serr != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch filterType {\n\t\tcase \"specific\":\n\t\t\tif storedDate.Format(\"2006-01-02\") == parsedDate.Format(\"2006-01-02\") {\n\t\t\t\tfilteredTasks = append(filteredTasks, task)\n\t\t\t}\n\t\tcase \"before\":\n\t\t\tif storedDate.Before(parsedDate) {\n\t\t\t\tfilteredTasks = append(filteredTasks, task)\n\t\t\t}\n\t\tcase \"after\":\n\t\t\tif storedDate.After(parsedDate) {\n\t\t\t\tfilteredTasks = append(filteredTasks, task)\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t})\n\n\t// Create a TasksObject with all collected tasks.\n\ttasksObject := &TasksObject{\n\t\tTasks: filteredTasks,\n\t}\n\n\t// Use the custom MarshalJSON method to marshal the tasks into JSON.\n\tmarshalledTasks, merr := tasksObject.MarshalJSON()\n\tif merr != nil {\n\t\treturn \"\"\n\t} \n\treturn string(marshalledTasks)\n}\n" + }, + { + "name": "tasks_test.gno", + "body": "package zentasktic\n\nimport (\n\t\"testing\"\n\n \"gno.land/p/demo/avl\"\n)\n\n// Shared instance of ZTaskManager\nvar ztm *ZTaskManager\n\nfunc init() {\n ztm = NewZTaskManager()\n}\n\nfunc Test_AddTask(t *testing.T) {\n task := Task{Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",}\n\n // Test adding a task successfully.\n err := ztm.AddTask(task)\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n // Test adding a duplicate task.\n cerr := ztm.AddTask(task)\n if cerr != ErrTaskIdAlreadyExists {\n t.Errorf(\"Expected ErrTaskIdAlreadyExists, got %v\", cerr)\n }\n}\n\nfunc Test_RemoveTask(t *testing.T) {\n \n task := Task{Id: \"20\", Body: \"Removable task\", RealmId: \"1\"}\n\n // Test adding a task successfully.\n err := ztm.AddTask(task)\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n retrievedTask, rerr := ztm.GetTaskById(task.Id)\n if rerr != nil {\n t.Errorf(\"Could not retrieve the added task\")\n }\n\n // Test removing a task\n terr := ztm.RemoveTask(retrievedTask)\n if terr != nil {\n t.Errorf(\"Expected nil, got %v\", terr)\n }\n}\n\nfunc Test_EditTask(t *testing.T) {\n \n task := Task{Id: \"2\", Body: \"First content\", RealmId: \"1\", ContextId: \"2\"}\n\n // Test adding a task successfully.\n err := ztm.AddTask(task)\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n // Test editing the task\n editedTask := Task{Id: task.Id, Body: \"Edited content\", RealmId: task.RealmId, ContextId: \"2\"}\n cerr := ztm.EditTask(editedTask)\n if cerr != nil {\n t.Errorf(\"Failed to edit the task\")\n }\n\n retrievedTask, _ := ztm.GetTaskById(editedTask.Id)\n if retrievedTask.Body != \"Edited content\" {\n t.Errorf(\"Task was not edited\")\n }\n}\n/*\nfunc Test_MoveTaskToRealm(t *testing.T) {\n \n task := Task{Id: \"3\", Body: \"First content\", RealmId: \"1\", ContextId: \"1\"}\n\n // Test adding a task successfully.\n err := task.AddTask()\n if err != nil {\n t.Errorf(\"Failed to add task: %v\", err)\n }\n\n // Test moving the task to another realm\n \n cerr := task.MoveTaskToRealm(\"2\")\n if cerr != nil {\n t.Errorf(\"Failed to move task to another realm\")\n }\n\n retrievedTask, _ := GetTaskById(task.Id)\n if retrievedTask.RealmId != \"2\" {\n t.Errorf(\"Task was moved to the wrong realm\")\n }\n}\n\nfunc Test_AttachTaskToProject(t *testing.T) {\n \n // Example Projects and Tasks\n prj := Project{Id: \"1\", Body: \"Project 1\", RealmId: \"1\",}\n tsk := Task{Id: \"4\", Body: \"Task 4\", RealmId: \"1\",}\n\n Projects.Set(prj.Id, prj) // Mock existing project\n\n tests := []struct {\n name string\n project Project\n task Task\n wantErr bool\n errMsg error\n }{\n {\n name: \"Attach to existing project\",\n project: prj,\n task: tsk,\n wantErr: false,\n },\n {\n name: \"Attach to non-existing project\",\n project: Project{Id: \"200\", Body: \"Project 200\", RealmId: \"1\",},\n task: tsk,\n wantErr: true,\n errMsg: ErrProjectIdNotFound,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := tt.task.AttachTaskToProject(tt.project)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"AttachTaskToProject() error = %v, wantErr %v\", err, tt.wantErr)\n }\n if tt.wantErr && err != tt.errMsg {\n t.Errorf(\"AttachTaskToProject() error = %v, expected %v\", err, tt.errMsg)\n }\n\n // For successful attach, verify the task is added to the project's tasks.\n if !tt.wantErr {\n tasks, exist := ProjectTasks.Get(tt.project.Id)\n if !exist || len(tasks.([]Task)) == 0 {\n t.Errorf(\"Task was not attached to the project\")\n } else {\n found := false\n for _, task := range tasks.([]Task) {\n if task.Id == tt.task.Id {\n found = true\n break\n }\n }\n if !found {\n t.Errorf(\"Task was not attached to the project\")\n }\n }\n }\n })\n }\n}\n\nfunc TestDetachTaskFromProject(t *testing.T) {\n\t\n\t// Setup:\n\tproject := Project{Id: \"p1\", Body: \"Test Project\"}\n\ttask1 := Task{Id: \"5\", Body: \"Task One\"}\n\ttask2 := Task{Id: \"6\", Body: \"Task Two\"}\n\n\tProjects.Set(project.Id, project)\n\tProjectTasks.Set(project.Id, []Task{task1, task2})\n\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\tproject Project\n\t\twantErr bool\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname: \"Detach existing task from project\",\n\t\t\ttask: task1,\n\t\t\tproject: project,\n\t\t\twantErr: false,\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to detach task from non-existing project\",\n\t\t\ttask: task1,\n\t\t\tproject: Project{Id: \"nonexistent\"},\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrProjectIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Try to detach non-existing task from project\",\n\t\t\ttask: Task{Id: \"nonexistent\"},\n\t\t\tproject: project,\n\t\t\twantErr: true,\n\t\t\texpectedErr: ErrTaskByIdNotFound,\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.task.DetachTaskFromProject(tt.project)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil || err != tt.expectedErr {\n\t\t\t\t\tt.Errorf(\"%s: expected error %v, got %v\", tt.name, tt.expectedErr, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: unexpected error: %v\", tt.name, err)\n\t\t\t\t}\n\n\t\t\t\t// For successful detachment, verify the task is no longer part of the project's tasks\n\t\t\t\tif !tt.wantErr {\n\t\t\t\t\ttasks, _ := ProjectTasks.Get(tt.project.Id)\n\t\t\t\t\tfor _, task := range tasks.([]Task) {\n\t\t\t\t\t\tif task.Id == tt.task.Id {\n\t\t\t\t\t\t\tt.Errorf(\"%s: task was not detached from the project\", tt.name)\n\t\t\t\t\t\t\tbreak\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}\n}\n\nfunc Test_SetTaskDueDate(t *testing.T) {\n\ttaskRealmIdOne, _ := GetTaskById(\"1\")\n taskRealmIdTwo, _ := GetTaskById(\"10\") \n\t// Define test cases\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\tdueDate string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"Task does not exist\",\n\t\t\ttask: Task{Id: \"nonexistent\", RealmId: \"2\"},\n\t\t\twantErr: ErrTaskIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Task not editable due to wrong realm\",\n\t\t\ttask: taskRealmIdOne,\n\t\t\twantErr: ErrTaskNotEditable,\n\t\t},\n\t\t{\n\t\t\tname: \"Successfully set due date\",\n\t\t\ttask: taskRealmIdTwo,\n\t\t\tdueDate: \"2023-01-01\",\n\t\t\twantErr: nil,\n\t\t},\n\t}\n\n\t// Execute test cases\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := tc.task.SetTaskDueDate(tc.dueDate)\n\n\t\t\t// Validate\n\t\t\tif err != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error %v, got %v\", tc.wantErr, err)\n\t\t\t}\n\n\t\t\t// Additional check for the success case to ensure the due date was actually set\n\t\t\tif err == nil {\n\t\t\t\t// Fetch the task again to check if the due date was set correctly\n\t\t\t\tupdatedTask, exist := Tasks.Get(tc.task.Id)\n\t\t\t\tif !exist {\n\t\t\t\t\tt.Fatalf(\"Task %v was not found after setting the due date\", tc.task.Id)\n\t\t\t\t}\n\t\t\t\tif updatedTask.(Task).Due != tc.dueDate {\n\t\t\t\t\tt.Errorf(\"Expected due date to be %v, got %v\", tc.dueDate, updatedTask.(Task).Due)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_SetTaskAlert(t *testing.T) {\n\ttaskRealmIdOne, _ := GetTaskById(\"1\")\n taskRealmIdTwo, _ := GetTaskById(\"10\") \n\t// Define test cases\n\ttests := []struct {\n\t\tname string\n\t\ttask Task\n\t\talertDate string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"Task does not exist\",\n\t\t\ttask: Task{Id: \"nonexistent\", RealmId: \"2\"},\n\t\t\twantErr: ErrTaskIdNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"Task not editable due to wrong realm\",\n\t\t\ttask: taskRealmIdOne,\n\t\t\twantErr: ErrTaskNotEditable,\n\t\t},\n\t\t{\n\t\t\tname: \"Successfully set alert\",\n\t\t\ttask: taskRealmIdTwo,\n\t\t\talertDate: \"2024-01-01\",\n\t\t\twantErr: nil,\n\t\t},\n\t}\n\n\t// Execute test cases\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := tc.task.SetTaskAlert(tc.alertDate)\n\n\t\t\t// Validate\n\t\t\tif err != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error %v, got %v\", tc.wantErr, err)\n\t\t\t}\n\n\t\t\t// Additional check for the success case to ensure the due date was actually set\n\t\t\tif err == nil {\n\t\t\t\t// Fetch the task again to check if the due date was set correctly\n\t\t\t\tupdatedTask, exist := Tasks.Get(tc.task.Id)\n\t\t\t\tif !exist {\n\t\t\t\t\tt.Fatalf(\"Task %v was not found after setting the due date\", tc.task.Id)\n\t\t\t\t}\n\t\t\t\tif updatedTask.(Task).Alert != tc.alertDate {\n\t\t\t\t\tt.Errorf(\"Expected due date to be %v, got %v\", tc.alertDate, updatedTask.(Task).Due)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// getters\n\nfunc Test_GetAllTasks(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n knownTasks := []Task{\n {Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",},\n {Id: \"10\", Body: \"First content\", RealmId: \"2\", ContextId: \"2\", Due: \"2023-01-01\", Alert: \"2024-01-01\"},\n {Id: \"2\", Body: \"Edited content\", RealmId: \"1\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable task\", RealmId: \"1\",},\n {Id: \"3\", Body: \"First content\", RealmId: \"2\", ContextId: \"1\",},\n {Id: \"40\", Body: \"Task 40\", RealmId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n tasksObject := TasksObject{Tasks: knownTasks}\n expected, err := tasksObject.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal known tasks: %v\", err)\n }\n\n // Execute GetAllTasks() to get the actual outcome.\n actual, err := GetAllTasks()\n if err != nil {\n t.Fatalf(\"GetAllTasks() failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual task JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetTasksByDate(t *testing.T) {\n\t\n\ttests := []struct {\n\t\tname string\n\t\ttaskDate string\n\t\tfilterType string\n\t\twant string\n\t\twantErr bool\n\t}{\n\t\t{\"SpecificDate\", \"2023-01-01\", \"specific\", `{\"tasks\":[{\"taskId\":\"10\",\"taskProjectId\":\"\",\"taskContextId\":\"2\",\"taskRealmId\":\"2\",\"taskBody\":\"First content\",\"taskDue\":\"2023-01-01\",\"taskAlert\":\"2024-01-01\"}]}`, false},\n\t\t{\"BeforeDate\", \"2022-04-05\", \"before\", \"\", false},\n\t\t{\"AfterDate\", \"2023-04-05\", \"after\", \"\", false},\n\t\t{\"NoMatch\", \"2002-04-07\", \"specific\", \"\", false},\n\t\t{\"InvalidDateFormat\", \"April 5, 2023\", \"specific\", \"\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := GetTasksByDate(tt.taskDate, tt.filterType)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetTasksByDate() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && got != tt.want {\n\t\t\t\tt.Errorf(\"GetTasksByDate() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_GetTaskById(t *testing.T){\n // test getting a non-existing task\n nonTask, err := GetTaskById(\"0\")\n if err != ErrTaskByIdNotFound {\n t.Fatalf(\"Expected ErrTaskByIdNotFound, got: %v\", err)\n }\n\n // test getting the correct task by id\n correctTask, err := GetTaskById(\"1\")\n if err != nil {\n t.Fatalf(\"Failed to get task by id, error: %v\", err)\n }\n\n if correctTask.Body != \"First task\" {\n t.Fatalf(\"Got the wrong task, with body: %v\", correctTask.Body)\n }\n}\n\nfunc Test_GetTasksByRealm(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n tasksInAssessRealm := []Task{\n {Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",},\n {Id: \"2\", RealmId: \"1\", Body: \"Edited content\", ContextId: \"2\",},\n {Id: \"20\", Body: \"Removable task\", RealmId: \"1\",},\n {Id: \"40\", Body: \"Task 40\", RealmId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n tasksObjectAssess := TasksObject{Tasks: tasksInAssessRealm}\n expected, err := tasksObjectAssess.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal tasks in Assess: %v\", err)\n }\n\n actual, err := GetTasksByRealm(\"1\")\n if err != nil {\n t.Fatalf(\"GetTasksByRealm('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual task JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n\nfunc Test_GetTasksByContext(t *testing.T) {\n \n // mocking the tasks based on previous tests\n // TODO: add isolation?\n tasksInContextOne := []Task{\n {Id: \"1\", RealmId: \"1\", Body: \"First task\", ContextId: \"1\",},\n {Id: \"3\", RealmId: \"2\", Body: \"First content\", ContextId: \"1\",},\n }\n\n // Manually marshal the known tasks to create the expected outcome.\n tasksObjectForContexts := TasksObject{Tasks: tasksInContextOne}\n expected, err := tasksObjectForContexts.MarshalJSON()\n if err != nil {\n t.Fatalf(\"Failed to manually marshal tasks for ContextId 1: %v\", err)\n }\n\n actual, err := GetTasksByContext(\"1\")\n if err != nil {\n t.Fatalf(\"GetTasksByContext('1') failed with error: %v\", err)\n }\n\n // Compare the expected and actual outcomes.\n if string(expected) != actual {\n t.Errorf(\"Expected and actual task JSON strings do not match.\\nExpected: %s\\nActual: %s\", string(expected), actual)\n }\n}\n*/\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "15000000", + "gas_fee": "1000000ugnot" + }, + "signatures": [], + "memo": "" +} + +-- tx3.tx -- +{ + "msg": [ + { + "@type": "/vm.m_addpkg", + "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "package": { + "name": "zentasktic_core", + "path": "gno.land/r/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic_core", + "files": [ + { + "name": "workable.gno", + "body": "package zentasktic_core\n\ntype Workable interface {\n\t// restrict implementation of Workable to this realm\n\tassertWorkable()\n}\n\ntype isWorkable struct {}\n\nfunc (wt *WorkableTask) assertWorkable() {}\n\nfunc (wp *WorkableProject) assertWorkable() {}\n\nvar _ Workable = &WorkableTask{}\nvar _ Workable = &WorkableProject{}\n" + }, + { + "name": "wrapper.gno", + "body": "package zentasktic_core\n\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic\"\n)\n\n// this is a convenience wrapper on top of the functions declared in the zentasktic package\n// to maintain consistency\n\n// wrapping zentasktic types\n\ntype WorkableTask struct {\n zentasktic.Task\n}\ntype WorkableProject struct {\n zentasktic.Project\n}\n\ntype WorkableRealm struct {\n\tId string\n\tName string\n}\n\ntype WorkableContext struct {\n\tzentasktic.Context\n}\n\ntype WorkableCollection struct {\n\tzentasktic.Collection\n}\n\ntype WorkableObjectPath struct {\n\tzentasktic.ObjectPath\n}\n\n// zentasktic managers\n\nvar ztm *zentasktic.ZTaskManager\nvar zpm *zentasktic.ZProjectManager\nvar zrm *zentasktic.ZRealmManager\nvar zcm *zentasktic.ZContextManager\nvar zcl *zentasktic.ZCollectionManager\nvar zom *zentasktic.ZObjectPathManager\nvar currentTaskID int\nvar currentProjectTaskID int\nvar currentProjectID int\nvar currentContextID int\nvar currentCollectionID int\nvar currentPathID int\n\nfunc init() {\n ztm = zentasktic.NewZTaskManager()\n zpm = zentasktic.NewZProjectManager()\n\tzrm = zentasktic.NewZRealmManager()\n\tzcm = zentasktic.NewZContextManager()\n\tzcl = zentasktic.NewZCollectionManager()\n\tzom = zentasktic.NewZObjectPathManager()\n\tcurrentTaskID = 0\n\tcurrentProjectTaskID = 0\n\tcurrentProjectID = 0\n\tcurrentContextID = 0\n\tcurrentCollectionID = 0\n\tcurrentPathID = 0\n}\n\n// tasks\n\nfunc AddTask(taskBody string) error {\n\ttaskID := incrementTaskID()\n\twt := &WorkableTask{\n\t\tTask: zentasktic.Task{\n\t\t\tId: strconv.Itoa(taskID),\n\t\t\tBody: taskBody,\n\t\t\tRealmId: \t \"1\",\n\t\t},\n\t}\n\treturn ztm.AddTask(wt.Task)\n}\n\n\nfunc EditTask(taskId string, taskBody string) error {\n\ttaskToEdit, err := GetTaskById(taskId)\n\tif err != nil {\n\t\treturn err\t\n\t}\n\ttaskToEdit.Body = taskBody;\n\treturn ztm.EditTask(taskToEdit.Task)\n}\n\nfunc RemoveTask(taskId string) error {\n\ttaskToRemove, err := GetTaskById(taskId)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn ztm.RemoveTask(taskToRemove.Task)\n}\n\nfunc MoveTaskToRealm(taskId string, realmId string) error {\n\treturn ztm.MoveTaskToRealm(taskId, realmId)\n}\n\nfunc SetTaskDueDate(taskId string, dueDate string) error {\n\treturn ztm.SetTaskDueDate(taskId, dueDate)\n}\n\nfunc SetTaskAlert(taskId string, alert string) error {\n\treturn ztm.SetTaskAlert(taskId, alert)\n}\n\nfunc AttachTaskToProject(taskBody string, projectId string) error {\n\tprojectTaskID := incrementProjectTaskID()\n\twt := &WorkableTask{\n\t\tTask: zentasktic.Task{\n\t\t\tId: strconv.Itoa(projectTaskID),\n\t\t\tBody: taskBody,\n\t\t\tRealmId: \t \"1\",\n\t\t},\n\t}\n\t//ztm.AddTask(wt.Task)\n\tprojectToAdd, err := GetProjectById(projectId)\n\tif err != nil {\n\t\treturn err\t\n\t}\n\treturn zpm.AttachTaskToProject(ztm, wt.Task, projectToAdd.Project)\n}\n\nfunc EditProjectTask(projectTaskId string, projectTaskBody string, projectId string) error {\n\treturn zpm.EditProjectTask(projectTaskId, projectTaskBody, projectId)\n}\n\nfunc DetachTaskFromProject(projectTaskId string, projectId string) error {\n\tprojectToDetachFrom, err := GetProjectById(projectId)\n\tif err != nil {\n\t\treturn err\t\n\t}\n\tdetachedTaskId := strconv.Itoa(incrementTaskID())\n\treturn zpm.DetachTaskFromProject(ztm, projectTaskId, detachedTaskId, projectToDetachFrom.Project)\n}\n\nfunc RemoveTaskFromProject(projectTaskId string, projectId string) error {\n\treturn zpm.RemoveTaskFromProject(projectTaskId, projectId)\n}\n\nfunc GetTaskById(taskId string) (WorkableTask, error) {\n\ttask, err := ztm.GetTaskById(taskId)\n\tif err != nil {\n\t\treturn WorkableTask{}, err\n\t}\n\treturn WorkableTask{Task: task}, nil\n}\n\nfunc GetAllTasks() (string){\n\treturn ztm.GetAllTasks()\n}\n\nfunc GetTasksByRealm(realmId string) (string){\n\treturn ztm.GetTasksByRealm(realmId)\n}\n\nfunc GetTasksByContextAndRealm(contextId string, realmId string) (string){\n\treturn ztm.GetTasksByContextAndRealm(contextId, realmId)\n}\n\nfunc GetTasksByDate(dueDate string, filterType string) (string){\n\treturn ztm.GetTasksByDate(dueDate, filterType)\n}\n\nfunc incrementTaskID() int {\n\tcurrentTaskID++\n\treturn currentTaskID\n}\n\nfunc incrementProjectTaskID() int {\n\tcurrentProjectTaskID++\n\treturn currentProjectTaskID\n}\n\n// projects\n\nfunc AddProject(projectBody string) error {\n\tprojectID := incrementProjectID()\n\twp := &WorkableProject{\n\t\tProject: zentasktic.Project{\n\t\t\tId: strconv.Itoa(projectID),\n\t\t\tBody: projectBody,\n\t\t\tRealmId: \t \"1\",\n\t\t},\n\t}\n\treturn zpm.AddProject(wp.Project)\n}\n\nfunc EditProject(projectId string, projectBody string) error {\n\tprojectToEdit, err := GetProjectById(projectId)\n\tif err != nil {\n\t\treturn err\t\n\t}\n\tprojectToEdit.Body = projectBody;\n\treturn zpm.EditProject(projectToEdit.Project)\n}\n\nfunc RemoveProject(projectId string) error {\n\tprojectToRemove, err := GetProjectById(projectId)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn zpm.RemoveProject(projectToRemove.Project)\n}\n\nfunc MoveProjectToRealm(projectId string, realmId string) error {\n\treturn zpm.MoveProjectToRealm(projectId, realmId)\n}\n\nfunc MarkProjectTaskAsDone(projectId string, projectTaskId string) error {\n\treturn zpm.MarkProjectTaskAsDone(projectId, projectTaskId)\n}\n\nfunc GetProjectTasks(wp WorkableProject) ([]WorkableTask, error){\n\ttasks, err := zpm.GetProjectTasks(wp.Project)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Convert []zentasktic.Task to []WorkableTask\n\tvar workableTasks []WorkableTask\n\tfor _, task := range tasks {\n\t\tworkableTasks = append(workableTasks, WorkableTask{Task: task})\n\t}\n\n\treturn workableTasks, nil\n}\n\nfunc SetProjectDueDate(projectId string, dueDate string) error {\n\treturn zpm.SetProjectDueDate(projectId, dueDate)\n}\n\nfunc GetProjectById(projectId string) (WorkableProject, error) {\n\tproject, err := zpm.GetProjectById(projectId)\n\tif err != nil {\n\t\treturn WorkableProject{}, err\n\t}\n\treturn WorkableProject{Project: project}, nil\n}\n\nfunc SetProjectTaskDueDate(projectId string, projectTaskId string, dueDate string) error {\n\treturn zpm.SetProjectTaskDueDate(projectId, projectTaskId, dueDate)\n}\n\nfunc GetAllProjects() (string){\n\treturn zpm.GetAllProjects()\n}\n\nfunc GetProjectsByRealm(realmId string) (string){\n\treturn zpm.GetProjectsByRealm(realmId)\n}\n\nfunc GetProjectsByContextAndRealm(contextId string, realmId string) (string){\n\treturn zpm.GetProjectsByContextAndRealm(contextId, realmId)\n}\n\nfunc GetProjectsByDate(dueDate string, filterType string) (string){\n\treturn zpm.GetProjectsByDate(dueDate, filterType)\n}\n\nfunc incrementProjectID() int {\n\tcurrentProjectID++\n\treturn currentProjectID\n}\n\n// realms\n\nfunc AddRealm(wr WorkableRealm) error {\n\tr := zentasktic.Realm{\n\t\tId: wr.Id,\n\t\tName: wr.Name,\n\t}\n\treturn zrm.AddRealm(r)\n}\n\nfunc RemoveRealm(wr WorkableRealm) error {\n\tr := zentasktic.Realm{\n\t\tId: wr.Id,\n\t\tName: wr.Name,\n\t}\n\treturn zrm.RemoveRealm(r)\n}\n\nfunc GetRealmById(realmId string) (WorkableRealm, error) {\n\tr, err := zrm.GetRealmById(realmId)\n\tif err != nil {\n\t\treturn WorkableRealm{}, err\n\t}\n\treturn WorkableRealm{\n\t\tId: r.Id,\n\t\tName: r.Name,\n\t}, nil\n}\n\nfunc GetAllRealms() (string, error) {\n\treturn zrm.GetRealms()\n}\n\n// contexts\n\nfunc AddContext(contextName string) error {\n\tcontextID := incrementContextID()\n\twc := &WorkableContext{\n\t\tContext: zentasktic.Context{\n\t\t\tId: strconv.Itoa(contextID),\n\t\t\tName: contextName,\n\t\t},\n\t}\n\treturn zcm.AddContext(wc.Context)\n}\n\nfunc EditContext(contextId string, newContext string) error {\n\tcontextToEdit, err := GetContextById(contextId)\n\tif err != nil {\n\t\treturn err\t\n\t}\n\tcontextToEdit.Name = newContext;\n\treturn zcm.EditContext(contextToEdit.Context)\n}\n\nfunc RemoveContext(contextId string) error {\n\tcontextToRemove, err := GetContextById(contextId)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn zcm.RemoveContext(contextToRemove.Context)\n}\n\nfunc AddContextToTask(contextId string, taskId string) error {\n\tcontextToAdd, err := GetContextById(contextId)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttaskToAddContextTo, merr := GetTaskById(taskId)\n\tif merr != nil {\n\t\treturn merr\n\t}\n\treturn zcm.AddContextToTask(ztm, contextToAdd.Context, taskToAddContextTo.Task)\n}\n\nfunc AddContextToProject(contextId string, projectId string) error {\n\tcontextToAdd, err := GetContextById(contextId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tprojectToAddContextTo, merr := GetProjectById(projectId)\n\tif merr != nil {\n\t\treturn merr\n\t}\n\treturn zcm.AddContextToProject(zpm, contextToAdd.Context, projectToAddContextTo.Project)\n}\n\nfunc AddContextToProjectTask(contextId string, projectId string, projectTaskId string) error {\n\tcontextToAdd, err := GetContextById(contextId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tprojectToAddContextTo, merr := GetProjectById(projectId)\n\tif merr != nil {\n\t\treturn merr\n\t}\n\treturn zcm.AddContextToProjectTask(zpm, contextToAdd.Context, projectToAddContextTo.Project, projectTaskId)\n}\n\nfunc GetContextById(contextId string) (WorkableContext, error) {\n\tcontext, err := zcm.GetContextById(contextId)\n\tif err != nil {\n\t\treturn WorkableContext{}, err\n\t}\n\treturn WorkableContext{Context: context}, nil\n}\n\nfunc GetAllContexts() (string) {\n\treturn zcm.GetAllContexts()\n}\n\nfunc incrementContextID() int {\n\tcurrentContextID++\n\treturn currentContextID\n}\n\n// collections\n/*\nfunc AddCollection(wc WorkableCollection) error {\n\tc := zentasktic.Collection{\n\t\tId: wc.Id,\n\t\tRealmId: wc.RealmId,\n\t\tName: wc.Name,\n\t\tTasks: toZentaskticTasks(wc.Tasks),\n\t\tProjects: toZentaskticProjects(wc.Projects),\n\t}\n\treturn zcl.AddCollection(c)\n}\n\nfunc EditCollection(wc WorkableCollection) error {\n\tc := zentasktic.Collection{\n\t\tId: wc.Id,\n\t\tRealmId: wc.RealmId,\n\t\tName: wc.Name,\n\t\tTasks: toZentaskticTasks(wc.Tasks),\n\t\tProjects: toZentaskticProjects(wc.Projects),\n\t}\n\treturn zcl.EditCollection(c)\n}\n\nfunc RemoveCollection(wc WorkableCollection) error {\n\tc := zentasktic.Collection{\n\t\tId: wc.Id,\n\t\tRealmId: wc.RealmId,\n\t\tName: wc.Name,\n\t\tTasks: toZentaskticTasks(wc.Tasks),\n\t\tProjects: toZentaskticProjects(wc.Projects),\n\t}\n\treturn zcl.RemoveCollection(c)\n}\n\nfunc GetCollectionById(collectionId string) (WorkableCollection, error) {\n\tc, err := zcl.GetCollectionById(collectionId)\n\tif err != nil {\n\t\treturn WorkableCollection{}, err\n\t}\n\treturn WorkableCollection{\n\t\tId: c.Id,\n\t\tRealmId: c.RealmId,\n\t\tName: c.Name,\n\t\tTasks: toWorkableTasks(c.Tasks),\n\t\tProjects: toWorkableProjects(c.Projects),\n\t}, nil\n}\n\nfunc GetCollectionTasks(wc WorkableCollection) ([]WorkableTask, error) {\n\tc := zentasktic.Collection{\n\t\tId: wc.Id,\n\t}\n\ttasks, err := zcl.GetCollectionTasks(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn toWorkableTasks(tasks), nil\n}\n\nfunc GetCollectionProjects(wc WorkableCollection) ([]WorkableProject, error) {\n\tc := zentasktic.Collection{\n\t\tId: wc.Id,\n\t}\n\tprojects, err := zcl.GetCollectionProjects(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn toWorkableProjects(projects), nil\n}\n\nfunc GetAllCollections() (string, error) {\n\treturn zcl.GetAllCollections()\n}\n\n// Helper functions to convert between Workable and zentasktic types\nfunc toZentaskticTasks(tasks []WorkableTask) []zentasktic.Task {\n\tztasks := make([]zentasktic.Task, len(tasks))\n\tfor i, t := range tasks {\n\t\tztasks[i] = t.Task\n\t}\n\treturn ztasks\n}\n\nfunc toWorkableTasks(tasks []zentasktic.Task) []WorkableTask {\n\twtasks := make([]WorkableTask, len(tasks))\n\tfor i, t := range tasks {\n\t\twtasks[i] = WorkableTask{Task: t}\n\t}\n\treturn wtasks\n}\n\nfunc toZentaskticProjects(projects []WorkableProject) []zentasktic.Project {\n\tzprojects := make([]zentasktic.Project, len(projects))\n\tfor i, p := range projects {\n\t\tzprojects[i] = p.Project\n\t}\n\treturn zprojects\n}\n\nfunc toWorkableProjects(projects []zentasktic.Project) []WorkableProject {\n\twprojects := make([]WorkableProject, len(projects))\n\tfor i, p := range projects {\n\t\twprojects[i] = WorkableProject{Project: p}\n\t}\n\treturn wprojects\n}*/\n\n// object Paths\n\nfunc AddPath(wop WorkableObjectPath) error {\n\to := zentasktic.ObjectPath{\n\t\tObjectType: wop.ObjectType,\n\t\tId: wop.Id,\n\t\tRealmId: wop.RealmId,\n\t}\n\treturn zom.AddPath(o)\n}\n\n\nfunc GetObjectJourney(objectType string, objectId string) (string, error) {\n\treturn zom.GetObjectJourney(objectType, objectId)\n}\n" + } + ] + }, + "deposit": "" + } + ], + "fee": { + "gas_wanted": "15000000", + "gas_fee": "1000000ugnot" + }, + "signatures": [], + "memo": "" +} + diff --git a/gno.land/cmd/gnoland/testdata/restart_nonval.txtar b/gno.land/cmd/gnoland/testdata/restart_nonval.txtar new file mode 100644 index 00000000000..87b4ad4ecb9 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/restart_nonval.txtar @@ -0,0 +1,5 @@ +# This txtar tests for starting up a non-validator node; then also restarting it. +loadpkg gno.land/p/demo/avl + +gnoland start -non-validator +gnoland restart 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/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 278055711b0..daf9fbdc5d4 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -1,12 +1,12 @@ -{"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.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":""} diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index 8aef07451d6..b7eb21837a7 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -6,14 +6,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 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/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" ) +var testGasFee = ugnot.ValueString(10000) + func TestRender(t *testing.T) { t.Parallel() testRealmPath := "gno.land/r/demo/deep/very/deep" @@ -88,25 +93,35 @@ func TestCallSingle(t *testing.T) { cfg := BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", } - msg := []MsgCall{ + caller, err := client.Signer.Info() + require.NoError(t, err) + + msg := []vm.MsgCall{ { - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{""}, - Send: "100ugnot", + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/deep/very/deep", + Func: "Render", + Args: []string{""}, + Send: std.Coins{{Denom: ugnot.Denom, Amount: int64(100)}}, }, } res, err := client.Call(cfg, msg...) assert.NoError(t, err) require.NotNil(t, res) - assert.Equal(t, string(res.DeliverTx.Data), "it works!") + expected := "it works!" + assert.Equal(t, string(res.DeliverTx.Data), expected) + + res, err = callSigningSeparately(t, client, cfg, msg...) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), expected) } func TestCallMultiple(t *testing.T) { @@ -147,47 +162,60 @@ func TestCallMultiple(t *testing.T) { cfg := BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", } - msg := []MsgCall{ + caller, err := client.Signer.Info() + require.NoError(t, err) + + msg := []vm.MsgCall{ { - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{""}, - Send: "100ugnot", + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/deep/very/deep", + Func: "Render", + Args: []string{""}, + Send: std.Coins{{Denom: ugnot.Denom, Amount: int64(100)}}, }, { - PkgPath: "gno.land/r/demo/wugnot", - FuncName: "Deposit", - Args: []string{""}, - Send: "1000ugnot", + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/wugnot", + Func: "Deposit", + Args: []string{""}, + Send: std.Coins{{Denom: ugnot.Denom, Amount: int64(1000)}}, }, { - PkgPath: "gno.land/r/demo/tamagotchi", - FuncName: "Feed", - Args: []string{""}, - Send: "", + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/tamagotchi", + Func: "Feed", + Args: []string{""}, + Send: nil, }, } res, err := client.Call(cfg, msg...) assert.NoError(t, err) assert.NotNil(t, res) + + res, err = callSigningSeparately(t, client, cfg, msg...) + assert.NoError(t, err) + assert.NotNil(t, res) } func TestCallErrors(t *testing.T) { t.Parallel() + // These tests don't actually sign + mockAddress, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + testCases := []struct { name string client Client cfg BaseTxCfg - msgs []MsgCall - expectedError error + msgs []vm.MsgCall + expectedError string }{ { name: "Invalid Signer", @@ -197,20 +225,21 @@ func TestCallErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, + Caller: mockAddress, + PkgPath: "gno.land/r/random/path", + Func: "RandomName", + Send: nil, + Args: []string{}, }, }, - expectedError: ErrMissingSigner, + expectedError: ErrMissingSigner.Error(), }, { name: "Invalid RPCClient", @@ -220,20 +249,21 @@ func TestCallErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, + Caller: mockAddress, + PkgPath: "gno.land/r/random/path", + Func: "RandomName", + Send: nil, + Args: []string{}, }, }, - expectedError: ErrMissingRPCClient, + expectedError: ErrMissingRPCClient.Error(), }, { name: "Invalid Gas Fee", @@ -248,13 +278,14 @@ func TestCallErrors(t *testing.T) { SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "random/path", - FuncName: "RandomName", + Caller: mockAddress, + PkgPath: "gno.land/r/random/path", + Func: "RandomName", }, }, - expectedError: ErrInvalidGasFee, + expectedError: ErrInvalidGasFee.Error(), }, { name: "Negative Gas Wanted", @@ -264,20 +295,21 @@ func TestCallErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: -1, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, + Caller: mockAddress, + PkgPath: "gno.land/r/random/path", + Func: "RandomName", + Send: nil, + Args: []string{}, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "0 Gas Wanted", @@ -287,20 +319,21 @@ func TestCallErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 0, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "random/path", - FuncName: "RandomName", - Send: "", - Args: []string{}, + Caller: mockAddress, + PkgPath: "gno.land/r/random/path", + Func: "RandomName", + Send: nil, + Args: []string{}, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "Invalid PkgPath", @@ -310,20 +343,21 @@ func TestCallErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "", - FuncName: "RandomName", - Send: "", - Args: []string{}, + Caller: mockAddress, + PkgPath: "", + Func: "RandomName", + Send: nil, + Args: []string{}, }, }, - expectedError: ErrEmptyPkgPath, + expectedError: vm.InvalidPkgPathError{}.Error(), }, { name: "Invalid FuncName", @@ -333,20 +367,21 @@ func TestCallErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgCall{ + msgs: []vm.MsgCall{ { - PkgPath: "random/path", - FuncName: "", - Send: "", - Args: []string{}, + Caller: mockAddress, + PkgPath: "gno.land/r/random/path", + Func: "", + Send: nil, + Args: []string{}, }, }, - expectedError: ErrEmptyFuncName, + expectedError: vm.InvalidExprError{}.Error(), }, } @@ -357,7 +392,7 @@ func TestCallErrors(t *testing.T) { res, err := tc.client.Call(tc.cfg, tc.msgs...) assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) + assert.ErrorContains(t, err, tc.expectedError) }) } } @@ -365,13 +400,16 @@ func TestCallErrors(t *testing.T) { func TestClient_Send_Errors(t *testing.T) { t.Parallel() + // These tests don't actually sign + mockAddress, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") testCases := []struct { name string client Client cfg BaseTxCfg - msgs []MsgSend - expectedError error + msgs []bank.MsgSend + expectedError string }{ { name: "Invalid Signer", @@ -381,18 +419,19 @@ func TestClient_Send_Errors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: toAddress, - Send: "1ugnot", + FromAddress: mockAddress, + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(1)}}, }, }, - expectedError: ErrMissingSigner, + expectedError: ErrMissingSigner.Error(), }, { name: "Invalid RPCClient", @@ -402,18 +441,19 @@ func TestClient_Send_Errors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: toAddress, - Send: "1ugnot", + FromAddress: mockAddress, + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(1)}}, }, }, - expectedError: ErrMissingRPCClient, + expectedError: ErrMissingRPCClient.Error(), }, { name: "Invalid Gas Fee", @@ -428,13 +468,14 @@ func TestClient_Send_Errors(t *testing.T) { SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: toAddress, - Send: "1ugnot", + FromAddress: mockAddress, + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(1)}}, }, }, - expectedError: ErrInvalidGasFee, + expectedError: ErrInvalidGasFee.Error(), }, { name: "Negative Gas Wanted", @@ -444,18 +485,19 @@ func TestClient_Send_Errors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: -1, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: toAddress, - Send: "1ugnot", + FromAddress: mockAddress, + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(1)}}, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "0 Gas Wanted", @@ -465,18 +507,19 @@ func TestClient_Send_Errors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 0, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: toAddress, - Send: "1ugnot", + FromAddress: mockAddress, + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(1)}}, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "Invalid To Address", @@ -495,18 +538,19 @@ func TestClient_Send_Errors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: crypto.Address{}, - Send: "1ugnot", + FromAddress: mockAddress, + ToAddress: crypto.Address{}, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(1)}}, }, }, - expectedError: ErrInvalidToAddress, + expectedError: std.InvalidAddressError{}.Error(), }, { name: "Invalid Send Coins", @@ -525,18 +569,19 @@ func TestClient_Send_Errors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgSend{ + msgs: []bank.MsgSend{ { - ToAddress: toAddress, - Send: "-1ugnot", + FromAddress: mockAddress, + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(-1)}}, }, }, - expectedError: ErrInvalidSendAmount, + expectedError: std.InvalidCoinsError{}.Error(), }, } @@ -547,7 +592,7 @@ func TestClient_Send_Errors(t *testing.T) { res, err := tc.client.Send(tc.cfg, tc.msgs...) assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) + assert.ErrorContains(t, err, tc.expectedError) }) } } @@ -586,7 +631,7 @@ func TestRunSingle(t *testing.T) { cfg := BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", @@ -602,7 +647,11 @@ func main() { println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) }` - msg := MsgRun{ + caller, err := client.Signer.Info() + require.NoError(t, err) + + msg := vm.MsgRun{ + Caller: caller.GetAddress(), Package: &std.MemPackage{ Files: []*std.MemFile{ { @@ -611,13 +660,19 @@ func main() { }, }, }, - Send: "", + Send: nil, } res, err := client.Run(cfg, msg) assert.NoError(t, err) require.NotNil(t, res) - assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) + expected := "hi gnoclient!\n" + assert.Equal(t, expected, string(res.DeliverTx.Data)) + + res, err = runSigningSeparately(t, client, cfg, msg) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, expected, string(res.DeliverTx.Data)) } func TestRunMultiple(t *testing.T) { @@ -653,7 +708,7 @@ func TestRunMultiple(t *testing.T) { cfg := BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", @@ -669,7 +724,11 @@ func main() { println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) }` - msg1 := MsgRun{ + caller, err := client.Signer.Info() + require.NoError(t, err) + + msg1 := vm.MsgRun{ + Caller: caller.GetAddress(), Package: &std.MemPackage{ Files: []*std.MemFile{ { @@ -678,10 +737,11 @@ func main() { }, }, }, - Send: "", + Send: nil, } - msg2 := MsgRun{ + msg2 := vm.MsgRun{ + Caller: caller.GetAddress(), Package: &std.MemPackage{ Files: []*std.MemFile{ { @@ -690,24 +750,33 @@ func main() { }, }, }, - Send: "", + Send: nil, } 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)) + expected := "hi gnoclient!\nhi gnoclient!\n" + assert.Equal(t, expected, string(res.DeliverTx.Data)) + + res, err = runSigningSeparately(t, client, cfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, expected, string(res.DeliverTx.Data)) } func TestRunErrors(t *testing.T) { t.Parallel() + // These tests don't actually sign + mockAddress, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + testCases := []struct { name string client Client cfg BaseTxCfg - msgs []MsgRun - expectedError error + msgs []vm.MsgRun + expectedError string }{ { name: "Invalid Signer", @@ -717,13 +786,14 @@ func TestRunErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgRun{ + msgs: []vm.MsgRun{ { + Caller: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -734,10 +804,10 @@ func TestRunErrors(t *testing.T) { }, }, }, - Send: "", + Send: nil, }, }, - expectedError: ErrMissingSigner, + expectedError: ErrMissingSigner.Error(), }, { name: "Invalid RPCClient", @@ -747,13 +817,13 @@ func TestRunErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgRun{}, - expectedError: ErrMissingRPCClient, + msgs: []vm.MsgRun{}, + expectedError: ErrMissingRPCClient.Error(), }, { name: "Invalid Gas Fee", @@ -768,8 +838,9 @@ func TestRunErrors(t *testing.T) { SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgRun{ + msgs: []vm.MsgRun{ { + Caller: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -780,10 +851,10 @@ func TestRunErrors(t *testing.T) { }, }, }, - Send: "", + Send: nil, }, }, - expectedError: ErrInvalidGasFee, + expectedError: ErrInvalidGasFee.Error(), }, { name: "Negative Gas Wanted", @@ -793,13 +864,14 @@ func TestRunErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: -1, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgRun{ + msgs: []vm.MsgRun{ { + Caller: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -810,10 +882,10 @@ func TestRunErrors(t *testing.T) { }, }, }, - Send: "", + Send: nil, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "0 Gas Wanted", @@ -823,13 +895,14 @@ func TestRunErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 0, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgRun{ + msgs: []vm.MsgRun{ { + Caller: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -840,10 +913,10 @@ func TestRunErrors(t *testing.T) { }, }, }, - Send: "", + Send: nil, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "Invalid Empty Package", @@ -862,18 +935,19 @@ func TestRunErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgRun{ + msgs: []vm.MsgRun{ { - Package: nil, - Send: "", + Caller: mockAddress, + Package: &std.MemPackage{Name: "", Path: " "}, + Send: nil, }, }, - expectedError: ErrEmptyPackage, + expectedError: vm.InvalidPkgPathError{}.Error(), }, } @@ -884,7 +958,7 @@ func TestRunErrors(t *testing.T) { res, err := tc.client.Run(tc.cfg, tc.msgs...) assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) + assert.ErrorContains(t, err, tc.expectedError) }) } } @@ -893,12 +967,15 @@ func TestRunErrors(t *testing.T) { func TestAddPackageErrors(t *testing.T) { t.Parallel() + // These tests don't actually sign + mockAddress, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + testCases := []struct { name string client Client cfg BaseTxCfg - msgs []MsgAddPackage - expectedError error + msgs []vm.MsgAddPackage + expectedError string }{ { name: "Invalid Signer", @@ -908,13 +985,14 @@ func TestAddPackageErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgAddPackage{ + msgs: []vm.MsgAddPackage{ { + Creator: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -925,10 +1003,10 @@ func TestAddPackageErrors(t *testing.T) { }, }, }, - Deposit: "", + Deposit: nil, }, }, - expectedError: ErrMissingSigner, + expectedError: ErrMissingSigner.Error(), }, { name: "Invalid RPCClient", @@ -938,13 +1016,13 @@ func TestAddPackageErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgAddPackage{}, - expectedError: ErrMissingRPCClient, + msgs: []vm.MsgAddPackage{}, + expectedError: ErrMissingRPCClient.Error(), }, { name: "Invalid Gas Fee", @@ -959,8 +1037,9 @@ func TestAddPackageErrors(t *testing.T) { SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgAddPackage{ + msgs: []vm.MsgAddPackage{ { + Creator: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -971,10 +1050,10 @@ func TestAddPackageErrors(t *testing.T) { }, }, }, - Deposit: "", + Deposit: nil, }, }, - expectedError: ErrInvalidGasFee, + expectedError: ErrInvalidGasFee.Error(), }, { name: "Negative Gas Wanted", @@ -984,13 +1063,14 @@ func TestAddPackageErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: -1, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgAddPackage{ + msgs: []vm.MsgAddPackage{ { + Creator: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -1001,10 +1081,10 @@ func TestAddPackageErrors(t *testing.T) { }, }, }, - Deposit: "", + Deposit: nil, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "0 Gas Wanted", @@ -1014,13 +1094,14 @@ func TestAddPackageErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 0, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgAddPackage{ + msgs: []vm.MsgAddPackage{ { + Creator: mockAddress, Package: &std.MemPackage{ Name: "", Path: "", @@ -1031,10 +1112,10 @@ func TestAddPackageErrors(t *testing.T) { }, }, }, - Deposit: "", + Deposit: nil, }, }, - expectedError: ErrInvalidGasWanted, + expectedError: ErrInvalidGasWanted.Error(), }, { name: "Invalid Empty Package", @@ -1053,18 +1134,19 @@ func TestAddPackageErrors(t *testing.T) { }, cfg: BaseTxCfg{ GasWanted: 100000, - GasFee: "10000ugnot", + GasFee: testGasFee, AccountNumber: 1, SequenceNumber: 1, Memo: "Test memo", }, - msgs: []MsgAddPackage{ + msgs: []vm.MsgAddPackage{ { - Package: nil, - Deposit: "", + Creator: mockAddress, + Package: &std.MemPackage{Name: "", Path: ""}, + Deposit: nil, }, }, - expectedError: ErrEmptyPackage, + expectedError: vm.InvalidPkgPathError{}.Error(), }, } @@ -1075,7 +1157,7 @@ func TestAddPackageErrors(t *testing.T) { res, err := tc.client.AddPackage(tc.cfg, tc.msgs...) assert.Nil(t, res) - assert.ErrorIs(t, err, tc.expectedError) + assert.ErrorContains(t, err, tc.expectedError) }) } } @@ -1266,3 +1348,63 @@ func TestLatestBlockHeightErrors(t *testing.T) { }) } } + +// The same as client.Call, but test signing separately +func callSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...vm.MsgCall) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewCallTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} + +// The same as client.Run, but test signing separately +func runSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...vm.MsgRun) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewRunTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} + +// The same as client.Send, but test signing separately +func sendSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...bank.MsgSend) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewSendTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} + +// The same as client.AddPackage, but test signing separately +func addPackageSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewAddPackageTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index a32a6899abe..9d3dbde22ae 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -4,22 +4,16 @@ 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") + ErrInvalidGasWanted = errors.New("invalid gas wanted") + ErrInvalidGasFee = errors.New("invalid gas fee") + ErrMissingSigner = errors.New("missing Signer") + ErrMissingRPCClient = errors.New("missing RPCClient") ) // BaseTxCfg defines the base transaction configuration, shared by all message types @@ -31,34 +25,8 @@ type BaseTxCfg struct { 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) { +func (c *Client) Call(cfg BaseTxCfg, msgs ...vm.MsgCall) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. if err := c.validateSigner(); err != nil { return nil, err @@ -67,38 +35,29 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx return nil, err } + tx, err := NewCallTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewCallTx makes an unsigned transaction from one or more MsgCall. +// The Caller field must be set. +func NewCallTx(cfg BaseTxCfg, msgs ...vm.MsgCall) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err } - // Parse MsgCall slice vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { // Validate MsgCall fields - if err := msg.validateMsgCall(); err != nil { - return nil, err - } - - // Parse send coins - send, err := std.ParseCoins(msg.Send) - if err != nil { - return nil, err - } - - caller, err := c.Signer.Info() - if err != nil { + if err := msg.ValidateBasic(); err != nil { return nil, err } - // Unwrap syntax sugar to vm.MsgCall slice - vmMsgs = append(vmMsgs, std.Msg(vm.MsgCall{ - Caller: caller.GetAddress(), - PkgPath: msg.PkgPath, - Func: msg.FuncName, - Args: msg.Args, - Send: send, - })) + vmMsgs = append(vmMsgs, std.Msg(msg)) } // Parse gas fee @@ -108,18 +67,16 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // Run executes one or more MsgRun calls on the blockchain -func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCommit, error) { +func (c *Client) Run(cfg BaseTxCfg, msgs ...vm.MsgRun) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. if err := c.validateSigner(); err != nil { return nil, err @@ -128,39 +85,29 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo return nil, err } + tx, err := NewRunTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewRunTx makes an unsigned transaction from one or more MsgRun. +// The Caller field must be set. +func NewRunTx(cfg BaseTxCfg, msgs ...vm.MsgRun) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err } - // Parse MsgRun slice vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { - // Validate MsgCall fields - if err := msg.validateMsgRun(); err != nil { + // Validate MsgRun fields + if err := msg.ValidateBasic(); err != nil { return nil, err } - // Parse send coins - send, err := std.ParseCoins(msg.Send) - if err != nil { - return nil, err - } - - 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.GetAddress(), - Package: msg.Package, - Send: send, - })) + vmMsgs = append(vmMsgs, std.Msg(msg)) } // Parse gas fee @@ -170,18 +117,16 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // Send executes one or more MsgSend calls on the blockchain -func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) { +func (c *Client) Send(cfg BaseTxCfg, msgs ...bank.MsgSend) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. if err := c.validateSigner(); err != nil { return nil, err @@ -190,36 +135,29 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx return nil, err } + tx, err := NewSendTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewSendTx makes an unsigned transaction from one or more MsgSend. +// The FromAddress field must be set. +func NewSendTx(cfg BaseTxCfg, msgs ...bank.MsgSend) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err } - // Parse MsgSend slice vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { // Validate MsgSend fields - if err := msg.validateMsgSend(); err != nil { - return nil, err - } - - // Parse send coins - send, err := std.ParseCoins(msg.Send) - if err != nil { - return nil, err - } - - caller, err := c.Signer.Info() - if err != nil { + if err := msg.ValidateBasic(); err != nil { return nil, err } - // Unwrap syntax sugar to vm.MsgSend slice - vmMsgs = append(vmMsgs, std.Msg(bank.MsgSend{ - FromAddress: caller.GetAddress(), - ToAddress: msg.ToAddress, - Amount: send, - })) + vmMsgs = append(vmMsgs, std.Msg(msg)) } // Parse gas fee @@ -229,18 +167,16 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // AddPackage executes one or more AddPackage calls on the blockchain -func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) { +func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. if err := c.validateSigner(); err != nil { return nil, err @@ -249,36 +185,29 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul return nil, err } + tx, err := NewAddPackageTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewAddPackageTx makes an unsigned transaction from one or more MsgAddPackage. +// The Creator field must be set. +func NewAddPackageTx(cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err } - // Parse MsgRun slice vmMsgs := make([]std.Msg, 0, len(msgs)) for _, msg := range msgs { - // Validate MsgCall fields - if err := msg.validateMsgAddPackage(); err != nil { + // Validate MsgAddPackage fields + if err := msg.ValidateBasic(); err != nil { return nil, err } - // Parse deposit coins - deposit, err := std.ParseCoins(msg.Deposit) - 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.MsgAddPackage{ - Creator: caller.GetAddress(), - Package: msg.Package, - Deposit: deposit, - })) + vmMsgs = append(vmMsgs, std.Msg(msg)) } // Parse gas fee @@ -288,18 +217,29 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // 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.SignTx(tx, accountNumber, sequenceNumber) + if err != nil { + return nil, err + } + return c.BroadcastTxCommit(signedTx) +} + +// SignTx signs a transaction and returns a signed tx ready for broadcasting. +// If accountNumber or sequenceNumber is 0 then query the blockchain for the value. +func (c *Client) SignTx(tx std.Tx, accountNumber, sequenceNumber uint64) (*std.Tx, error) { + if err := c.validateSigner(); err != nil { + return nil, err + } caller, err := c.Signer.Info() if err != nil { return nil, err @@ -323,7 +263,15 @@ func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumb if err != nil { return nil, errors.Wrap(err, "sign") } + return signedTx, nil +} +// BroadcastTxCommit marshals and broadcasts the signed transaction, returning the result. +// If the result has a delivery error, then return a wrapped error. +func (c *Client) BroadcastTxCommit(signedTx *std.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + if err := c.validateRPCClient(); err != nil { + return nil, err + } bz, err := amino.Marshal(signedTx) if err != nil { return nil, errors.Wrap(err, "marshaling tx binary bytes") diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index 9bcba7997ae..ea068e0680b 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -5,9 +5,12 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -36,28 +39,37 @@ func TestCallSingle_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, Memo: "", } + caller, err := client.Signer.Info() + require.NoError(t, err) + // Make Msg config - msg := MsgCall{ - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{"test argument"}, - Send: "", + msg := vm.MsgCall{ + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/deep/very/deep", + Func: "Render", + Args: []string{"test argument"}, + Send: nil, } // 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.Nil(t, err) + assert.Equal(t, expected, got) + + res, err = callSigningSeparately(t, client, baseCfg, msg) + require.NoError(t, err) + got = string(res.DeliverTx.Data) assert.Equal(t, expected, got) } @@ -80,36 +92,46 @@ func TestCallMultiple_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, Memo: "", } + caller, err := client.Signer.Info() + require.NoError(t, err) + // Make Msg configs - msg1 := MsgCall{ - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{""}, - Send: "", + msg1 := vm.MsgCall{ + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/deep/very/deep", + Func: "Render", + Args: []string{""}, + Send: nil, } // Same call, different argument - msg2 := MsgCall{ - PkgPath: "gno.land/r/demo/deep/very/deep", - FuncName: "Render", - Args: []string{"test argument"}, - Send: "", + msg2 := vm.MsgCall{ + Caller: caller.GetAddress(), + PkgPath: "gno.land/r/demo/deep/very/deep", + Func: "Render", + Args: []string{"test argument"}, + Send: nil, } expected := "(\"it works!\" string)\n\n(\"hi test argument\" string)\n\n" // Execute call res, err := client.Call(baseCfg, msg1, msg2) + require.NoError(t, err) got := string(res.DeliverTx.Data) - assert.Nil(t, err) + assert.Equal(t, expected, got) + + res, err = callSigningSeparately(t, client, baseCfg, msg1, msg2) + require.NoError(t, err) + got = string(res.DeliverTx.Data) assert.Equal(t, expected, got) } @@ -132,34 +154,49 @@ func TestSendSingle_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, Memo: "", } + caller, err := client.Signer.Info() + require.NoError(t, err) + // Make Send config for a new address on the blockchain toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") amount := 10 - msg := MsgSend{ - ToAddress: toAddress, - Send: std.Coin{"ugnot", int64(amount)}.String(), + msg := bank.MsgSend{ + FromAddress: caller.GetAddress(), + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(amount)}}, } // 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)}} + expected := std.Coins{{Denom: ugnot.Denom, Amount: int64(amount)}} got := account.GetCoins() assert.Equal(t, expected, got) + + res, err = sendSigningSeparately(t, client, baseCfg, msg) + require.NoError(t, err) + assert.Equal(t, "", string(res.DeliverTx.Data)) + + // Get the new account balance + account, _, err = client.QueryAccount(toAddress) + require.NoError(t, err) + expected2 := std.Coins{{Denom: ugnot.Denom, Amount: int64(2 * amount)}} + got = account.GetCoins() + assert.Equal(t, expected2, got) } func TestSendMultiple_Integration(t *testing.T) { @@ -181,26 +218,31 @@ func TestSendMultiple_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, Memo: "", } + caller, err := client.Signer.Info() + require.NoError(t, err) + // Make Msg configs toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p") amount1 := 10 - msg1 := MsgSend{ - ToAddress: toAddress, - Send: std.Coin{"ugnot", int64(amount1)}.String(), + msg1 := bank.MsgSend{ + FromAddress: caller.GetAddress(), + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(amount1)}}, } // Same send, different argument amount2 := 20 - msg2 := MsgSend{ - ToAddress: toAddress, - Send: std.Coin{"ugnot", int64(amount2)}.String(), + msg2 := bank.MsgSend{ + FromAddress: caller.GetAddress(), + ToAddress: toAddress, + Amount: std.Coins{{Denom: ugnot.Denom, Amount: int64(amount2)}}, } // Execute send @@ -212,10 +254,21 @@ func TestSendMultiple_Integration(t *testing.T) { account, _, err := client.QueryAccount(toAddress) assert.NoError(t, err) - expected := std.Coins{{"ugnot", int64(amount1 + amount2)}} + expected := std.Coins{{Denom: ugnot.Denom, Amount: int64(amount1 + amount2)}} got := account.GetCoins() assert.Equal(t, expected, got) + + res, err = sendSigningSeparately(t, client, baseCfg, msg1, msg2) + require.NoError(t, err) + assert.Equal(t, "", string(res.DeliverTx.Data)) + + // Get the new account balance + account, _, err = client.QueryAccount(toAddress) + require.NoError(t, err) + expected2 := std.Coins{{Denom: ugnot.Denom, Amount: int64(2 * (amount1 + amount2))}} + got = account.GetCoins() + assert.Equal(t, expected2, got) } // Run tests @@ -237,7 +290,7 @@ func TestRunSingle_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, @@ -257,9 +310,14 @@ func main() { println(ufmt.Sprintf("- after: %d", tests.Counter())) }` + caller, err := client.Signer.Info() + require.NoError(t, err) + // Make Msg configs - msg := MsgRun{ + msg := vm.MsgRun{ + Caller: caller.GetAddress(), Package: &std.MemPackage{ + Name: "main", Files: []*std.MemFile{ { Name: "main.gno", @@ -267,13 +325,18 @@ func main() { }, }, }, - Send: "", + Send: nil, } res, err := client.Run(baseCfg, msg) assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, string(res.DeliverTx.Data), "- before: 0\n- after: 10\n") + + res, err = runSigningSeparately(t, client, baseCfg, msg) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "- before: 10\n- after: 20\n") } // Run tests @@ -295,7 +358,7 @@ func TestRunMultiple_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, @@ -324,9 +387,14 @@ func main() { println(ufmt.Sprintf("%s", deep.Render("gnoclient!"))) }` + caller, err := client.Signer.Info() + require.NoError(t, err) + // Make Msg configs - msg1 := MsgRun{ + msg1 := vm.MsgRun{ + Caller: caller.GetAddress(), Package: &std.MemPackage{ + Name: "main", Files: []*std.MemFile{ { Name: "main.gno", @@ -334,10 +402,12 @@ func main() { }, }, }, - Send: "", + Send: nil, } - msg2 := MsgRun{ + msg2 := vm.MsgRun{ + Caller: caller.GetAddress(), Package: &std.MemPackage{ + Name: "main", Files: []*std.MemFile{ { Name: "main.gno", @@ -345,7 +415,7 @@ func main() { }, }, }, - Send: "", + Send: nil, } expected := "- before: 0\n- after: 10\nhi gnoclient!\n" @@ -354,6 +424,12 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, expected, string(res.DeliverTx.Data)) + + res, err = runSigningSeparately(t, client, baseCfg, msg1, msg2) + require.NoError(t, err) + require.NotNil(t, res) + expected2 := "- before: 10\n- after: 20\nhi gnoclient!\n" + assert.Equal(t, expected2, string(res.DeliverTx.Data)) } func TestAddPackageSingle_Integration(t *testing.T) { @@ -375,7 +451,7 @@ func TestAddPackageSingle_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, @@ -390,10 +466,14 @@ func Echo(str string) string { fileName := "echo.gno" deploymentPath := "gno.land/p/demo/integration/test/echo" - deposit := "100ugnot" + deposit := std.Coins{{Denom: ugnot.Denom, Amount: int64(100)}} + + caller, err := client.Signer.Info() + require.NoError(t, err) // Make Msg config - msg := MsgAddPackage{ + msg := vm.MsgAddPackage{ + Creator: caller.GetAddress(), Package: &std.MemPackage{ Name: "echo", Path: deploymentPath, @@ -409,7 +489,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{ @@ -422,7 +502,19 @@ func Echo(str string) string { // Query balance to validate deposit baseAcc, _, err := client.QueryAccount(gnolang.DerivePkgAddr(deploymentPath)) require.NoError(t, err) - assert.Equal(t, baseAcc.GetCoins().String(), deposit) + assert.Equal(t, baseAcc.GetCoins(), deposit) + + // Test signing separately (using a different deployment path) + deploymentPathB := "gno.land/p/demo/integration/test/echo2" + msg.Package.Path = deploymentPathB + _, err = addPackageSigningSeparately(t, client, baseCfg, msg) + assert.NoError(t, err) + query, err = client.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPathB), + }) + require.NoError(t, err) + assert.Equal(t, string(query.Response.Data), fileName) } func TestAddPackageMultiple_Integration(t *testing.T) { @@ -444,14 +536,14 @@ func TestAddPackageMultiple_Integration(t *testing.T) { // Make Tx config baseCfg := BaseTxCfg{ - GasFee: "10000ugnot", + GasFee: ugnot.ValueString(10000), GasWanted: 8000000, AccountNumber: 0, SequenceNumber: 0, Memo: "", } - deposit := "100ugnot" + deposit := std.Coins{{Denom: ugnot.Denom, Amount: int64(100)}} deploymentPath1 := "gno.land/p/demo/integration/test/echo" body1 := `package echo @@ -467,7 +559,11 @@ func Hello(str string) string { return "Hello " + str + "!" }` - msg1 := MsgAddPackage{ + caller, err := client.Signer.Info() + require.NoError(t, err) + + msg1 := vm.MsgAddPackage{ + Creator: caller.GetAddress(), Package: &std.MemPackage{ Name: "echo", Path: deploymentPath1, @@ -478,10 +574,11 @@ func Hello(str string) string { }, }, }, - Deposit: "", + Deposit: nil, } - msg2 := MsgAddPackage{ + msg2 := vm.MsgAddPackage{ + Creator: caller.GetAddress(), Package: &std.MemPackage{ Name: "hello", Path: deploymentPath2, @@ -501,7 +598,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{ @@ -528,7 +625,28 @@ func Hello(str string) string { // Query balance to validate deposit baseAcc, _, err = client.QueryAccount(gnolang.DerivePkgAddr(deploymentPath2)) require.NoError(t, err) - assert.Equal(t, baseAcc.GetCoins().String(), deposit) + assert.Equal(t, baseAcc.GetCoins(), deposit) + + // Test signing separately (using a different deployment path) + deploymentPath1B := "gno.land/p/demo/integration/test/echo2" + deploymentPath2B := "gno.land/p/demo/integration/test/hello2" + msg1.Package.Path = deploymentPath1B + msg2.Package.Path = deploymentPath2B + _, err = addPackageSigningSeparately(t, client, baseCfg, msg1, msg2) + assert.NoError(t, err) + query, err = client.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath1B), + }) + require.NoError(t, err) + assert.Equal(t, string(query.Response.Data), "echo.gno") + query, err = client.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath2B), + }) + require.NoError(t, err) + assert.Contains(t, string(query.Response.Data), "hello.gno") + assert.Contains(t, string(query.Response.Data), "gno.mod") } // todo add more integration tests: diff --git a/gno.land/pkg/gnoclient/signer.go b/gno.land/pkg/gnoclient/signer.go index 0462865f2be..6e652080c72 100644 --- a/gno.land/pkg/gnoclient/signer.go +++ b/gno.land/pkg/gnoclient/signer.go @@ -3,6 +3,7 @@ package gnoclient import ( "fmt" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/errors" @@ -47,7 +48,7 @@ func (s SignerFromKeybase) Validate() error { signCfg := SignCfg{ UnsignedTX: std.Tx{ Msgs: []std.Msg{msg}, - Fee: std.NewFee(0, std.NewCoin("ugnot", 1000000)), + Fee: std.NewFee(0, std.NewCoin(ugnot.Denom, 1000000)), }, } if _, err = s.Sign(signCfg); err != nil { diff --git a/gno.land/pkg/gnoclient/util.go b/gno.land/pkg/gnoclient/util.go index 177e6d92906..50099eb4bd8 100644 --- a/gno.land/pkg/gnoclient/util.go +++ b/gno.land/pkg/gnoclient/util.go @@ -1,7 +1,5 @@ package gnoclient -import "github.com/gnolang/gno/tm2/pkg/std" - func (cfg BaseTxCfg) validateBaseTxConfig() error { if cfg.GasWanted <= 0 { return ErrInvalidGasWanted @@ -12,42 +10,3 @@ func (cfg BaseTxCfg) validateBaseTxConfig() error { return nil } - -func (msg MsgCall) validateMsgCall() error { - if msg.PkgPath == "" { - return ErrEmptyPkgPath - } - if msg.FuncName == "" { - return ErrEmptyFuncName - } - - return nil -} - -func (msg MsgSend) validateMsgSend() error { - if msg.ToAddress.IsZero() { - return ErrInvalidToAddress - } - _, err := std.ParseCoins(msg.Send) - if err != nil { - return ErrInvalidSendAmount - } - - return nil -} - -func (msg MsgRun) validateMsgRun() error { - if msg.Package == nil || len(msg.Package.Files) == 0 { - return ErrEmptyPackage - } - - return nil -} - -func (msg MsgAddPackage) validateMsgAddPackage() error { - if msg.Package == nil || len(msg.Package.Files) == 0 { - return ErrEmptyPackage - } - - return nil -} diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index ac066fa98b8..2380658c6e9 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -1,3 +1,4 @@ +// Package gnoland contains the bootstrapping code to launch a gno.land node. package gnoland import ( @@ -5,6 +6,7 @@ import ( "log/slog" "path/filepath" "strconv" + "time" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -25,48 +27,46 @@ import ( // Only goleveldb is supported for now. _ "github.com/gnolang/gno/tm2/pkg/db/_tags" _ "github.com/gnolang/gno/tm2/pkg/db/goleveldb" - "github.com/gnolang/gno/tm2/pkg/db/memdb" ) +// AppOptions contains the options to create the gno.land ABCI application. type AppOptions struct { - DB dbm.DB - // `gnoRootDir` should point to the local location of the gno repository. - // It serves as the gno equivalent of GOROOT. - 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 + DB dbm.DB // required + Logger *slog.Logger // required + EventSwitch events.EventSwitch // required + MaxCycles int64 // hard limit for cycles in GnoVM + InitChainerConfig // options related to InitChainer } -func NewAppOptions() *AppOptions { +// DefaultAppOptions provides a "ready" default [AppOptions] for use with +// [NewAppWithOptions], using the provided db. +func TestAppOptions(db dbm.DB) *AppOptions { return &AppOptions{ - GenesisTxHandler: PanicOnFailingTxHandler, - Logger: log.NewNoopLogger(), - DB: memdb.NewMemDB(), - GnoRootDir: gnoenv.RootDir(), - EventSwitch: events.NilEventSwitch(), + DB: db, + Logger: log.NewNoopLogger(), + EventSwitch: events.NewEventSwitch(), + InitChainerConfig: InitChainerConfig{ + GenesisTxResultHandler: PanicOnFailingTxResultHandler, + StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"), + CacheStdlibLoad: true, + }, } } -func (c *AppOptions) validate() error { - if c.Logger == nil { - return fmt.Errorf("no logger provided") - } - - if c.DB == nil { +func (c AppOptions) validate() error { + // Required fields + switch { + case c.DB == nil: return fmt.Errorf("no db provided") + case c.Logger == nil: + return fmt.Errorf("no logger provided") + case c.EventSwitch == nil: + return fmt.Errorf("no event switch provided") } - return nil } -// NewAppWithOptions creates the GnoLand application with specified options +// NewAppWithOptions creates the gno.land application with specified options. func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { if err := cfg.validate(); err != nil { return nil, err @@ -88,13 +88,13 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Construct keepers. acctKpr := auth.NewAccountKeeper(mainKey, ProtoGnoAccount) bankKpr := bank.NewBankKeeper(acctKpr) - - // XXX: Embed this ? - stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") - vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) + vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, cfg.MaxCycles) // Set InitChainer - baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.GenesisTxHandler)) + icc := cfg.InitChainerConfig + icc.baseApp = baseApp + icc.acctKpr, icc.bankKpr, icc.vmKpr = acctKpr, bankKpr, vmk + baseApp.SetInitChainer(icc.InitChainer) // Set AnteHandler authOptions := auth.AnteOptions{ @@ -108,14 +108,28 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { newCtx sdk.Context, res sdk.Result, abort bool, ) { // Override auth params. - ctx = ctx.WithValue( - auth.AuthParamsContextKey{}, auth.DefaultParams()) + ctx = ctx. + WithValue(auth.AuthParamsContextKey{}, auth.DefaultParams()) // Continue on with default auth ante handler. newCtx, res, abort = authAnteHandler(ctx, tx, simulate) return }, ) + // Set begin and end transaction hooks. + // These are used to create gno transaction stores and commit them when finishing + // the tx - in other words, data from a failing transaction won't be persisted + // to the gno store caches. + baseApp.SetBeginTxHook(func(ctx sdk.Context) sdk.Context { + // Create Gno transaction store. + return vmk.MakeGnoTransactionStore(ctx) + }) + baseApp.SetEndTxHook(func(ctx sdk.Context, result sdk.Result) { + if result.IsOK() { + vmk.CommitGnoTransactionStore(ctx) + } + }) + // Set up the event collector c := newCollector[validatorUpdate]( cfg.EventSwitch, // global event switch filled by the node @@ -143,13 +157,13 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Initialize the VMKeeper. ms := baseApp.GetCacheMultiStore() - vmk.Initialize(cfg.Logger, ms, cfg.CacheStdlibLoad) + vmk.Initialize(cfg.Logger, ms) ms.MultiWrite() // XXX why was't this needed? return baseApp, nil } -// NewApp creates the GnoLand application. +// NewApp creates the gno.land application. func NewApp( dataRootDir string, skipFailingGenesisTxs bool, @@ -158,9 +172,16 @@ func NewApp( ) (abci.Application, error) { var err error - cfg := NewAppOptions() + cfg := &AppOptions{ + Logger: logger, + EventSwitch: evsw, + InitChainerConfig: InitChainerConfig{ + GenesisTxResultHandler: PanicOnFailingTxResultHandler, + StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"), + }, + } if skipFailingGenesisTxs { - cfg.GenesisTxHandler = NoopGenesisTxHandler + cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler } // Get main DB. @@ -169,65 +190,135 @@ func NewApp( return nil, fmt.Errorf("error initializing database %q using path %q: %w", dbm.GoLevelDBBackend, dataRootDir, err) } - cfg.Logger = logger - cfg.EventSwitch = evsw - return NewAppWithOptions(cfg) } -type GenesisTxHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result) +// GenesisTxResultHandler is called in the InitChainer after a genesis +// transaction is executed. +type GenesisTxResultHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result) -func NoopGenesisTxHandler(_ sdk.Context, _ std.Tx, _ sdk.Result) {} +// NoopGenesisTxResultHandler is a no-op GenesisTxResultHandler. +func NoopGenesisTxResultHandler(_ sdk.Context, _ std.Tx, _ sdk.Result) {} -func PanicOnFailingTxHandler(_ sdk.Context, _ std.Tx, res sdk.Result) { +// PanicOnFailingTxResultHandler handles genesis transactions by panicking if +// res.IsErr() returns true. +func PanicOnFailingTxResultHandler(_ 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 { - return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - 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, - ) - } - - resHandler(ctx, tx, res) - } - } +// InitChainerConfig keeps the configuration for the InitChainer. +// [NewAppWithOptions] will set [InitChainerConfig.InitChainer] as its InitChainer +// function. +type InitChainerConfig struct { + // Handles the results of each genesis transaction. + GenesisTxResultHandler + + // Standard library directory. + StdlibDir string + // Whether to keep a record of the DB operations to load standard libraries, + // so they can be quickly replicated on additional genesis executions. + // This should be used for integration testing, where InitChainer will be + // called several times. + CacheStdlibLoad bool - // Done! + // These fields are passed directly by NewAppWithOptions, and should not be + // configurable by end-users. + baseApp *sdk.BaseApp + vmKpr vm.VMKeeperI + acctKpr auth.AccountKeeperI + bankKpr bank.BankKeeperI +} + +// InitChainer is the function that can be used as a [sdk.InitChainer]. +func (cfg InitChainerConfig) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + start := time.Now() + ctx.Logger().Debug("InitChainer: started") + + // load standard libraries; immediately committed to store so that they are + // available for use when processing the genesis transactions below. + cfg.loadStdlibs(ctx) + ctx.Logger().Debug("InitChainer: standard libraries loaded", + "elapsed", time.Since(start)) + + // load app state. AppState may be nil mostly in some minimal testing setups; + // so log a warning when that happens. + txResponses, err := cfg.loadAppState(ctx, req.AppState) + if err != nil { return abci.ResponseInitChain{ - Validators: req.Validators, + ResponseBase: abci.ResponseBase{ + Error: abci.StringError(err.Error()), + }, } } + + ctx.Logger().Debug("InitChainer: genesis transactions loaded", + "elapsed", time.Since(start)) + + // Done! + return abci.ResponseInitChain{ + Validators: req.Validators, + TxResponses: txResponses, + } +} + +func (cfg InitChainerConfig) loadStdlibs(ctx sdk.Context) { + // cache-wrapping is necessary for non-validator nodes; in the tm2 BaseApp, + // this is done using BaseApp.cacheTxContext; so we replicate it here. + ms := ctx.MultiStore() + msCache := ms.MultiCacheWrap() + + stdlibCtx := cfg.vmKpr.MakeGnoTransactionStore(ctx) + stdlibCtx = stdlibCtx.WithMultiStore(msCache) + if cfg.CacheStdlibLoad { + cfg.vmKpr.LoadStdlibCached(stdlibCtx, cfg.StdlibDir) + } else { + cfg.vmKpr.LoadStdlib(stdlibCtx, cfg.StdlibDir) + } + cfg.vmKpr.CommitGnoTransactionStore(stdlibCtx) + + msCache.MultiWrite() +} + +func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci.ResponseDeliverTx, error) { + state, ok := appState.(GnoGenesisState) + if !ok { + return nil, fmt.Errorf("invalid AppState of type %T", appState) + } + + // Parse and set genesis state balances + for _, bal := range state.Balances { + acc := cfg.acctKpr.NewAccountWithAddress(ctx, bal.Address) + cfg.acctKpr.SetAccount(ctx, acc) + err := cfg.bankKpr.SetCoins(ctx, bal.Address, bal.Amount) + if err != nil { + panic(err) + } + } + + txResponses := make([]abci.ResponseDeliverTx, 0, len(state.Txs)) + // Run genesis txs + for _, tx := range state.Txs { + res := cfg.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, + }) + + cfg.GenesisTxResultHandler(ctx, tx, res) + } + return txResponses, nil } // endBlockerApp is the app abstraction required by any EndBlocker diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 852d090f3af..193ff0b0b14 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -1,20 +1,202 @@ package gnoland import ( + "context" "errors" "fmt" "strings" "testing" + "time" - "github.com/gnolang/gno/gnovm/stdlibs/std" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + gnostd "github.com/gnolang/gno/gnovm/stdlibs/std" + "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - "github.com/gnolang/gno/tm2/pkg/bft/types" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" + "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/iavl" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// Tests that NewAppWithOptions works even when only providing a simple DB. +func TestNewAppWithOptions(t *testing.T) { + t.Parallel() + + app, err := NewAppWithOptions(TestAppOptions(memdb.NewMemDB())) + require.NoError(t, err) + bapp := app.(*sdk.BaseApp) + assert.Equal(t, "dev", bapp.AppVersion()) + assert.Equal(t, "gnoland", bapp.Name()) + + addr := crypto.AddressFromPreimage([]byte("test1")) + resp := bapp.InitChain(abci.RequestInitChain{ + Time: time.Now(), + ChainID: "dev", + ConsensusParams: &abci.ConsensusParams{ + Block: defaultBlockParams(), + }, + Validators: []abci.ValidatorUpdate{}, + AppState: GnoGenesisState{ + Balances: []Balance{ + { + Address: addr, + Amount: []std.Coin{{Amount: 1e15, Denom: "ugnot"}}, + }, + }, + Txs: []std.Tx{ + { + Msgs: []std.Msg{vm.NewMsgAddPackage(addr, "gno.land/r/demo", []*std.MemFile{ + { + Name: "demo.gno", + Body: "package demo; func Hello() string { return `hello`; }", + }, + })}, + Fee: std.Fee{GasWanted: 1e6, GasFee: std.Coin{Amount: 1e6, Denom: "ugnot"}}, + Signatures: []std.Signature{{}}, // one empty signature + }, + }, + }, + }) + require.True(t, resp.IsOK(), "InitChain response: %v", resp) + + tx := amino.MustMarshal(std.Tx{ + Msgs: []std.Msg{vm.NewMsgCall(addr, nil, "gno.land/r/demo", "Hello", nil)}, + Fee: std.Fee{ + GasWanted: 100_000, + GasFee: std.Coin{ + Denom: "ugnot", + Amount: 1_000_000, + }, + }, + Signatures: []std.Signature{{}}, // one empty signature + Memo: "", + }) + dtxResp := bapp.DeliverTx(abci.RequestDeliverTx{ + RequestBase: abci.RequestBase{}, + Tx: tx, + }) + require.True(t, dtxResp.IsOK(), "DeliverTx response: %v", dtxResp) +} + +func TestNewAppWithOptions_ErrNoDB(t *testing.T) { + t.Parallel() + + _, err := NewAppWithOptions(&AppOptions{}) + assert.ErrorContains(t, err, "no db provided") +} + +func TestNewApp(t *testing.T) { + // NewApp should have good defaults and manage to run InitChain. + td := t.TempDir() + + app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger()) + require.NoError(t, err, "NewApp should be successful") + + resp := app.InitChain(abci.RequestInitChain{ + RequestBase: abci.RequestBase{}, + Time: time.Time{}, + ChainID: "dev", + ConsensusParams: &abci.ConsensusParams{ + Block: defaultBlockParams(), + Validator: &abci.ValidatorParams{ + PubKeyTypeURLs: []string{}, + }, + }, + Validators: []abci.ValidatorUpdate{}, + AppState: GnoGenesisState{}, + }) + assert.True(t, resp.IsOK(), "resp is not OK: %v", resp) +} + +// Test whether InitChainer calls to load the stdlibs correctly. +func TestInitChainer_LoadStdlib(t *testing.T) { + t.Parallel() + + t.Run("cached", func(t *testing.T) { testInitChainerLoadStdlib(t, true) }) + t.Run("uncached", func(t *testing.T) { testInitChainerLoadStdlib(t, false) }) +} + +func testInitChainerLoadStdlib(t *testing.T, cached bool) { //nolint:thelper + t.Parallel() + + type gsContextType string + const ( + stdlibDir = "test-stdlib-dir" + gnoStoreKey gsContextType = "gno-store-key" + gnoStoreValue gsContextType = "gno-store-value" + ) + db := memdb.NewMemDB() + ms := store.NewCommitMultiStore(db) + baseCapKey := store.NewStoreKey("baseCapKey") + iavlCapKey := store.NewStoreKey("iavlCapKey") + + ms.MountStoreWithDB(baseCapKey, dbadapter.StoreConstructor, db) + ms.MountStoreWithDB(iavlCapKey, iavl.StoreConstructor, db) + ms.LoadLatestVersion() + testCtx := sdk.NewContext(sdk.RunTxModeDeliver, ms.MultiCacheWrap(), &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) + + // mock set-up + var ( + makeCalls int + commitCalls int + loadStdlibCalls int + loadStdlibCachedCalls int + ) + containsGnoStore := func(ctx sdk.Context) bool { + return ctx.Context().Value(gnoStoreKey) == gnoStoreValue + } + // ptr is pointer to either loadStdlibCalls or loadStdlibCachedCalls + loadStdlib := func(ptr *int) func(ctx sdk.Context, dir string) { + return func(ctx sdk.Context, dir string) { + assert.Equal(t, stdlibDir, dir, "stdlibDir should match provided dir") + assert.True(t, containsGnoStore(ctx), "should contain gno store") + *ptr++ + } + } + mock := &mockVMKeeper{ + makeGnoTransactionStoreFn: func(ctx sdk.Context) sdk.Context { + makeCalls++ + assert.False(t, containsGnoStore(ctx), "should not already contain gno store") + return ctx.WithContext(context.WithValue(ctx.Context(), gnoStoreKey, gnoStoreValue)) + }, + commitGnoTransactionStoreFn: func(ctx sdk.Context) { + commitCalls++ + assert.True(t, containsGnoStore(ctx), "should contain gno store") + }, + loadStdlibFn: loadStdlib(&loadStdlibCalls), + loadStdlibCachedFn: loadStdlib(&loadStdlibCachedCalls), + } + + // call initchainer + cfg := InitChainerConfig{ + StdlibDir: stdlibDir, + vmKpr: mock, + CacheStdlibLoad: cached, + } + cfg.InitChainer(testCtx, abci.RequestInitChain{ + AppState: GnoGenesisState{}, + }) + + // assert number of calls + assert.Equal(t, 1, makeCalls, "should call MakeGnoTransactionStore once") + assert.Equal(t, 1, commitCalls, "should call CommitGnoTransactionStore once") + if cached { + assert.Equal(t, 0, loadStdlibCalls, "should call LoadStdlib never") + assert.Equal(t, 1, loadStdlibCachedCalls, "should call LoadStdlibCached once") + } else { + assert.Equal(t, 1, loadStdlibCalls, "should call LoadStdlib once") + assert.Equal(t, 0, loadStdlibCachedCalls, "should call LoadStdlibCached never") + } +} + // generateValidatorUpdates generates dummy validator updates func generateValidatorUpdates(t *testing.T, count int) []abci.ValidatorUpdate { t.Helper() @@ -81,7 +263,7 @@ func TestEndBlocker(t *testing.T) { t.Run("no collector events", func(t *testing.T) { t.Parallel() - noFilter := func(e events.Event) []validatorUpdate { + noFilter := func(_ events.Event) []validatorUpdate { return []validatorUpdate{} } @@ -102,7 +284,7 @@ func TestEndBlocker(t *testing.T) { t.Parallel() var ( - noFilter = func(e events.Event) []validatorUpdate { + noFilter = func(_ events.Event) []validatorUpdate { return make([]validatorUpdate, 1) // 1 update } @@ -126,7 +308,7 @@ func TestEndBlocker(t *testing.T) { c := newCollector[validatorUpdate](mockEventSwitch, noFilter) // Fire a GnoVM event - mockEventSwitch.FireEvent(std.GnoEvent{}) + mockEventSwitch.FireEvent(gnostd.GnoEvent{}) // Create the EndBlocker eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) @@ -145,7 +327,7 @@ func TestEndBlocker(t *testing.T) { t.Parallel() var ( - noFilter = func(e events.Event) []validatorUpdate { + noFilter = func(_ events.Event) []validatorUpdate { return make([]validatorUpdate, 1) // 1 update } @@ -169,7 +351,7 @@ func TestEndBlocker(t *testing.T) { c := newCollector[validatorUpdate](mockEventSwitch, noFilter) // Fire a GnoVM event - mockEventSwitch.FireEvent(std.GnoEvent{}) + mockEventSwitch.FireEvent(gnostd.GnoEvent{}) // Create the EndBlocker eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) @@ -208,7 +390,7 @@ func TestEndBlocker(t *testing.T) { // Construct the GnoVM events vmEvents := make([]abci.Event, 0, len(changes)) for index := range changes { - event := std.GnoEvent{ + event := gnostd.GnoEvent{ Type: validatorAddedEvent, PkgPath: valRealm, } @@ -217,7 +399,7 @@ func TestEndBlocker(t *testing.T) { if index%2 == 0 { changes[index].Power = 0 - event = std.GnoEvent{ + event = gnostd.GnoEvent{ Type: validatorRemovedEvent, PkgPath: valRealm, } @@ -227,8 +409,8 @@ func TestEndBlocker(t *testing.T) { } // Fire the tx result event - txEvent := types.EventTx{ - Result: types.TxResult{ + txEvent := bft.EventTx{ + Result: bft.TxResult{ Response: abci.ResponseDeliverTx{ ResponseBase: abci.ResponseBase{ Events: vmEvents, diff --git a/gno.land/pkg/gnoland/balance_test.go b/gno.land/pkg/gnoland/balance_test.go index 59dffcc4333..99a348e9f2f 100644 --- a/gno.land/pkg/gnoland/balance_test.go +++ b/gno.land/pkg/gnoland/balance_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -78,7 +79,7 @@ func TestBalance_Parse(t *testing.T) { func TestBalance_AminoUnmarshalJSON(t *testing.T) { expected := Balance{ Address: crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - Amount: std.MustParseCoins("100ugnot"), + Amount: std.MustParseCoins(ugnot.ValueString(100)), } value := fmt.Sprintf("[%q]", expected.String()) @@ -95,7 +96,7 @@ func TestBalance_AminoUnmarshalJSON(t *testing.T) { func TestBalance_AminoMarshalJSON(t *testing.T) { expected := Balance{ Address: crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - Amount: std.MustParseCoins("100ugnot"), + Amount: std.MustParseCoins(ugnot.ValueString(100)), } expectedJSON := fmt.Sprintf("[%q]", expected.String()) @@ -112,15 +113,15 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) { // Generate dummy keys dummyKeys := getDummyKeys(t, 2) - amount := std.NewCoins(std.NewCoin("ugnot", 10)) + amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) entries := make([]string, len(dummyKeys)) for index, key := range dummyKeys { entries[index] = fmt.Sprintf( - "%s=%dugnot", + "%s=%s", key.Address().String(), - amount.AmountOf("ugnot"), + ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ) } @@ -150,7 +151,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) { t.Parallel() balances := []string{ - "dummyaddress=10ugnot", + "dummyaddress=" + ugnot.ValueString(10), } balanceMap, err := GetBalancesFromEntries(balances...) @@ -165,9 +166,10 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) { balances := []string{ fmt.Sprintf( - "%s=%sugnot", + "%s=%s%s", dummyKey.Address().String(), strconv.FormatUint(math.MaxUint64, 10), + ugnot.Denom, ), } @@ -185,15 +187,15 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) { // Generate dummy keys dummyKeys := getDummyKeys(t, 2) - amount := std.NewCoins(std.NewCoin("ugnot", 10)) + amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) balances := make([]string, len(dummyKeys)) for index, key := range dummyKeys { balances[index] = fmt.Sprintf( - "%s=%dugnot", + "%s=%s", key.Address().String(), - amount.AmountOf("ugnot"), + ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ) } @@ -215,9 +217,10 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) { balances := []string{ fmt.Sprintf( - "%s=%sugnot", + "%s=%s%s", dummyKey.Address().String(), strconv.FormatUint(math.MaxUint64, 10), + ugnot.Denom, ), } diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go index 1ff9f168bd1..62aecaf5278 100644 --- a/gno.land/pkg/gnoland/mock_test.go +++ b/gno.land/pkg/gnoland/mock_test.go @@ -45,18 +45,15 @@ func (m *mockEventSwitch) RemoveListener(listenerID string) { } } -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 + addPackageFn func(sdk.Context, vm.MsgAddPackage) error + callFn func(sdk.Context, vm.MsgCall) (string, error) + queryFn func(sdk.Context, string, string) (string, error) + runFn func(sdk.Context, vm.MsgRun) (string, error) + loadStdlibFn func(sdk.Context, string) + loadStdlibCachedFn func(sdk.Context, string) + makeGnoTransactionStoreFn func(ctx sdk.Context) sdk.Context + commitGnoTransactionStoreFn func(ctx sdk.Context) } func (m *mockVMKeeper) AddPackage(ctx sdk.Context, msg vm.MsgAddPackage) error { @@ -91,6 +88,31 @@ func (m *mockVMKeeper) Run(ctx sdk.Context, msg vm.MsgRun) (res string, err erro return "", nil } +func (m *mockVMKeeper) LoadStdlib(ctx sdk.Context, stdlibDir string) { + if m.loadStdlibFn != nil { + m.loadStdlibFn(ctx, stdlibDir) + } +} + +func (m *mockVMKeeper) LoadStdlibCached(ctx sdk.Context, stdlibDir string) { + if m.loadStdlibCachedFn != nil { + m.loadStdlibCachedFn(ctx, stdlibDir) + } +} + +func (m *mockVMKeeper) MakeGnoTransactionStore(ctx sdk.Context) sdk.Context { + if m.makeGnoTransactionStoreFn != nil { + return m.makeGnoTransactionStoreFn(ctx) + } + return ctx +} + +func (m *mockVMKeeper) CommitGnoTransactionStore(ctx sdk.Context) { + if m.commitGnoTransactionStoreFn != nil { + m.commitGnoTransactionStoreFn(ctx) + } +} + type ( lastBlockHeightDelegate func() int64 loggerDelegate func() *slog.Logger diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 02691f89c3e..d168c955607 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -3,6 +3,7 @@ package gnoland import ( "fmt" "log/slog" + "path/filepath" "time" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -22,8 +23,11 @@ type InMemoryNodeConfig struct { PrivValidator bft.PrivValidator // identity of the validator Genesis *bft.GenesisDoc TMConfig *tmcfg.Config - GenesisTxHandler GenesisTxHandler GenesisMaxVMCycles int64 + DB *memdb.MemDB // will be initialized if nil + + // If StdlibDir not set, then it's filepath.Join(TMConfig.RootDir, "gnovm", "stdlibs") + InitChainerConfig } // NewMockedPrivValidator generate a new key @@ -37,12 +41,7 @@ func NewDefaultGenesisConfig(chainid string) *bft.GenesisDoc { GenesisTime: time.Now(), ChainID: chainid, ConsensusParams: abci.ConsensusParams{ - Block: &abci.BlockParams{ - MaxTxBytes: 1_000_000, // 1MB, - MaxDataBytes: 2_000_000, // 2MB, - MaxGas: 100_000_000, // 100M gas - TimeIotaMS: 100, // 100ms - }, + Block: defaultBlockParams(), }, AppState: &GnoGenesisState{ Balances: []Balance{}, @@ -51,6 +50,15 @@ func NewDefaultGenesisConfig(chainid string) *bft.GenesisDoc { } } +func defaultBlockParams() *abci.BlockParams { + return &abci.BlockParams{ + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 100_000_000, // 100M gas + TimeIotaMS: 100, // 100ms + } +} + func NewDefaultTMConfig(rootdir string) *tmcfg.Config { // We use `TestConfig` here otherwise ChainID will be empty, and // there is no other way to update it than using a config file @@ -70,7 +78,7 @@ func (cfg *InMemoryNodeConfig) validate() error { return fmt.Errorf("`TMConfig.RootDir` is required to locate `stdlibs` directory") } - if cfg.GenesisTxHandler == nil { + if cfg.GenesisTxResultHandler == nil { return fmt.Errorf("`GenesisTxHandler` is required but not provided") } @@ -87,15 +95,21 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, evsw := events.NewEventSwitch() + if cfg.StdlibDir == "" { + cfg.StdlibDir = filepath.Join(cfg.TMConfig.RootDir, "gnovm", "stdlibs") + } + // initialize db if nil + if cfg.DB == nil { + cfg.DB = memdb.NewMemDB() + } + // Initialize the application with the provided options gnoApp, err := NewAppWithOptions(&AppOptions{ - Logger: logger, - GnoRootDir: cfg.TMConfig.RootDir, - GenesisTxHandler: cfg.GenesisTxHandler, - MaxCycles: cfg.GenesisMaxVMCycles, - DB: memdb.NewMemDB(), - EventSwitch: evsw, - CacheStdlibLoad: true, + Logger: logger, + MaxCycles: cfg.GenesisMaxVMCycles, + DB: cfg.DB, + EventSwitch: evsw, + InitChainerConfig: cfg.InitChainerConfig, }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) @@ -114,7 +128,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, // Create genesis factory genProvider := func() (*bft.GenesisDoc, error) { return cfg.Genesis, nil } - dbProvider := func(*node.DBContext) (db.DB, error) { return memdb.NewMemDB(), nil } + dbProvider := func(*node.DBContext) (db.DB, error) { return cfg.DB, nil } // Generate p2p node identity nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} diff --git a/gno.land/pkg/gnoland/ugnot/denom.go b/gno.land/pkg/gnoland/ugnot/denom.go new file mode 100644 index 00000000000..734c8532398 --- /dev/null +++ b/gno.land/pkg/gnoland/ugnot/denom.go @@ -0,0 +1,11 @@ +package ugnot + +import "strconv" + +// Denom is the denomination for ugnot, gno.land's native token. +const Denom = "ugnot" + +// ValueString converts `value` to a string, appends "ugnot", and returns it. +func ValueString(value int64) string { + return strconv.FormatInt(value, 10) + Denom +} diff --git a/gno.land/pkg/gnoweb/alias.go b/gno.land/pkg/gnoweb/alias.go new file mode 100644 index 00000000000..7fb28d5cbc3 --- /dev/null +++ b/gno.land/pkg/gnoweb/alias.go @@ -0,0 +1,27 @@ +package gnoweb + +// realm aliases +var Aliases = map[string]string{ + "/": "/r/gnoland/home", + "/about": "/r/gnoland/pages:p/about", + "/gnolang": "/r/gnoland/pages:p/gnolang", + "/ecosystem": "/r/gnoland/pages:p/ecosystem", + "/partners": "/r/gnoland/pages:p/partners", + "/testnets": "/r/gnoland/pages:p/testnets", + "/start": "/r/gnoland/pages:p/start", + "/license": "/r/gnoland/pages:p/license", + "/contribute": "/r/gnoland/pages:p/contribute", + "/events": "/r/gnoland/events", +} + +// http redirects +var Redirects = map[string]string{ + "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary + "/blog": "/r/gnoland/blog", + "/gor": "/contribute", + "/game-of-realms": "/contribute", + "/grants": "/partners", + "/language": "/gnolang", + "/getting-started": "/start", + "/gophercon24": "https://docs.gno.land", +} diff --git a/gno.land/pkg/gnoweb/gnoweb.go b/gno.land/pkg/gnoweb/gnoweb.go index b997de7840d..5377ae6a420 100644 --- a/gno.land/pkg/gnoweb/gnoweb.go +++ b/gno.land/pkg/gnoweb/gnoweb.go @@ -1,6 +1,7 @@ package gnoweb import ( + "bytes" "embed" "encoding/json" "errors" @@ -79,34 +80,11 @@ func MakeApp(logger *slog.Logger, cfg Config) gotuna.App { Static: static.EmbeddedStatic, } - // realm aliases - aliases := map[string]string{ - "/": "/r/gnoland/home", - "/about": "/r/gnoland/pages:p/about", - "/gnolang": "/r/gnoland/pages:p/gnolang", - "/ecosystem": "/r/gnoland/pages:p/ecosystem", - "/partners": "/r/gnoland/pages:p/partners", - "/testnets": "/r/gnoland/pages:p/testnets", - "/start": "/r/gnoland/pages:p/start", - "/license": "/r/gnoland/pages:p/license", - "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm - "/events": "/r/gnoland/events", - } - - for from, to := range aliases { + for from, to := range Aliases { app.Router.Handle(from, handlerRealmAlias(logger, app, &cfg, to)) } - // http redirects - redirects := map[string]string{ - "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary - "/blog": "/r/gnoland/blog", - "/gor": "/game-of-realms", - "/grants": "/partners", - "/language": "/gnolang", - "/getting-started": "/start", - "/gophercon24": "https://docs.gno.land", - } - for from, to := range redirects { + + for from, to := range Redirects { app.Router.Handle(from, handlerRedirect(logger, app, &cfg, to)) } // realm routes @@ -335,6 +313,15 @@ func handleRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config, w http. return } } + + dirdata := []byte(rlmpath) + dirres, err := makeRequest(logger, cfg, qFileStr, dirdata) + if err != nil { + writeError(logger, w, err) + return + } + hasReadme := bytes.Contains(append(dirres.Data, '\n'), []byte("README.md\n")) + // linkify querystr. queryParts := strings.Split(querystr, "/") pathLinks := []pathLink{} @@ -354,6 +341,7 @@ func handleRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config, w http. tmpl.Set("PathLinks", pathLinks) tmpl.Set("Contents", string(res.Data)) tmpl.Set("Config", cfg) + tmpl.Set("HasReadme", hasReadme) tmpl.Render(w, r, "realm_render.html", "funcs.html") } diff --git a/gno.land/pkg/gnoweb/gnoweb_test.go b/gno.land/pkg/gnoweb/gnoweb_test.go index d6b93b37d69..18df5ec2356 100644 --- a/gno.land/pkg/gnoweb/gnoweb_test.go +++ b/gno.land/pkg/gnoweb/gnoweb_test.go @@ -38,8 +38,9 @@ func TestRoutes(t *testing.T) { {"/r/demo/deep/very/deep?help", ok, "exposed"}, {"/r/demo/deep/very/deep/", ok, "render.gno"}, {"/r/demo/deep/very/deep/render.gno", ok, "func Render("}, - {"/game-of-realms", ok, "/r/gnoland/pages:p/gor"}, - {"/gor", found, "/game-of-realms"}, + {"/contribute", ok, "Game of Realms"}, + {"/game-of-realms", found, "/contribute"}, + {"/gor", found, "/contribute"}, {"/blog", found, "/r/gnoland/blog"}, {"/404-not-found", notFound, "/404-not-found"}, {"/아스키문자가아닌경로", notFound, "/아스키문자가아닌경로"}, @@ -49,7 +50,9 @@ func TestRoutes(t *testing.T) { {"/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() @@ -96,7 +99,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/js/renderer.js b/gno.land/pkg/gnoweb/static/js/renderer.js index 4937b5a5691..0aa6400633d 100644 --- a/gno.land/pkg/gnoweb/static/js/renderer.js +++ b/gno.land/pkg/gnoweb/static/js/renderer.js @@ -7,27 +7,42 @@ function renderUsernames(raw) { return raw.replace(/( |\n)@([_a-z0-9]{5,16})/, "$1[@$2](/r/demo/users:$2)"); } -function parseContent(source) { - const { markedHighlight } = globalThis.markedHighlight; - const { Marked } = globalThis.marked; - const markedInstance = new Marked( - markedHighlight({ - langPrefix: 'language-', - highlight(code, lang, info) { - if (lang === "json") { - try { - code = JSON.stringify(JSON.parse(code), null, 2); - } catch {} - } - const language = hljs.getLanguage(lang) ? lang : 'plaintext'; - return hljs.highlight(code, { language }).value; - } - }) - ); - markedInstance.setOptions({ gfm: true }); - const doc = new DOMParser().parseFromString(source, "text/html"); - const contents = doc.documentElement.textContent; - return markedInstance.parse(contents); +function parseContent(source, isCode) { + if (isCode) { + const highlightedCode = hljs.highlightAuto(source).value; + const codeElement = document.createElement("code"); + codeElement.classList.add("hljs"); + codeElement.innerHTML = highlightedCode; + + const preElement = document.createElement("pre"); + preElement.appendChild(codeElement); + + return preElement; + } else { + const { markedHighlight } = globalThis.markedHighlight; + const { Marked } = globalThis.marked; + const markedInstance = new Marked( + markedHighlight({ + langPrefix: "language-", + highlight(code, lang, info) { + if (lang === "json") { + try { + code = JSON.stringify(JSON.parse(code), null, 2); + } catch { + console.error('Error: The provided JSON code is invalid.'); + } + } + const language = hljs.getLanguage(lang) ? lang : "plaintext"; + return hljs.highlight(code, { language }).value; + }, + }) + ); + markedInstance.setOptions({ gfm: true }); + const doc = new DOMParser().parseFromString(source, "text/html"); + const contents = doc.documentElement.textContent; + + return markedInstance.parse(contents); + } } /* diff --git a/gno.land/pkg/gnoweb/views/funcs.html b/gno.land/pkg/gnoweb/views/funcs.html index a02f83144f8..d676fec9a69 100644 --- a/gno.land/pkg/gnoweb/views/funcs.html +++ b/gno.land/pkg/gnoweb/views/funcs.html @@ -15,7 +15,7 @@
  • Blog
  • Docs
  • Playground
  • -
  • Game of Realms
  • +
  • Contribute
  • @@ -144,11 +144,11 @@ -{{- end -}} +{{- end -}} {{- define "footer" -}}
    - {{ template "logo" }} + {{ template "logo" }}
    {{- end -}} @@ -160,18 +160,20 @@ diff --git a/gno.land/pkg/gnoweb/views/package_file.html b/gno.land/pkg/gnoweb/views/package_file.html index 42e1d0a28fc..43e7820b29f 100644 --- a/gno.land/pkg/gnoweb/views/package_file.html +++ b/gno.land/pkg/gnoweb/views/package_file.html @@ -11,21 +11,13 @@
    {{ .Data.DirPath }}/{{ .Data.FileName }}
    -
    -
    {{ .Data.FileContents }}
    + {{ .Data.FileContents }}
    {{ template "footer" }}
    {{ template "js" .}} - - {{- end -}} diff --git a/gno.land/pkg/gnoweb/views/realm_render.html b/gno.land/pkg/gnoweb/views/realm_render.html index 9a4507777a6..924ef2b414f 100644 --- a/gno.land/pkg/gnoweb/views/realm_render.html +++ b/gno.land/pkg/gnoweb/views/realm_render.html @@ -16,6 +16,9 @@ {{- end -}} + {{ if .Data.HasReadme }} + [readme] + {{ end }} [source] [help] diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index bea0fe78349..ef3ed9923da 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -8,9 +8,12 @@ // // Additional Command Overview: // -// 1. `gnoland [start|stop]`: +// 1. `gnoland [start|stop|restart]`: // - The gnoland node doesn't start automatically. This enables the user to do some // pre-configuration or pass custom arguments to the start command. +// - `gnoland restart` will simulate restarting a node, as in stopping and +// starting it again, recovering state from the persisted database data. +// - `gnoland start -non-validator` can be used to start a node as a non-validator node. // // 2. `gnokey`: // - Supports most of the common commands. @@ -45,6 +48,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/gnoland.txtar b/gno.land/pkg/integration/testdata/gnoland.txtar index c675e7578b6..78bdc9cae4e 100644 --- a/gno.land/pkg/integration/testdata/gnoland.txtar +++ b/gno.land/pkg/integration/testdata/gnoland.txtar @@ -28,7 +28,7 @@ cmp stderr gnoland-already-stop.stderr.golden -- gnoland-no-arguments.stdout.golden -- -- gnoland-no-arguments.stderr.golden -- -"gnoland" error: syntax: gnoland [start|stop] +"gnoland" error: syntax: gnoland [start|stop|restart] -- gnoland-start.stdout.golden -- node started successfully -- gnoland-start.stderr.golden -- 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/testdata/restart.txtar b/gno.land/pkg/integration/testdata/restart.txtar new file mode 100644 index 00000000000..8d50dd15814 --- /dev/null +++ b/gno.land/pkg/integration/testdata/restart.txtar @@ -0,0 +1,24 @@ +# simple test for the `gnoland restart` command; +# should restart the gno.land node and recover state. + +loadpkg gno.land/r/demo/counter $WORK +gnoland start + +gnokey maketx call -pkgpath gno.land/r/demo/counter -func Incr -gas-fee 1000000ugnot -gas-wanted 100000 -broadcast -chainid tendermint_test test1 +stdout '\(1 int\)' + +gnoland restart + +gnokey maketx call -pkgpath gno.land/r/demo/counter -func Incr -gas-fee 1000000ugnot -gas-wanted 100000 -broadcast -chainid tendermint_test test1 +stdout '\(2 int\)' + +-- counter.gno -- +package counter + +var counter int + +func Incr() int { + counter++ + return counter +} + diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 654dda0b45e..d3f55cfadf7 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -3,6 +3,7 @@ package integration import ( "context" "errors" + "flag" "fmt" "hash/crc32" "log/slog" @@ -13,8 +14,10 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "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" @@ -24,6 +27,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/bip39" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/db/memdb" tm2Log "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/std" "github.com/rogpeppe/go-internal/testscript" @@ -70,6 +74,7 @@ func RunGnolandTestscripts(t *testing.T, txtarDir string) { type testNode struct { *node.Node + cfg *gnoland.InMemoryNodeConfig nGnoKeyExec uint // Counter for execution of gnokey. } @@ -150,7 +155,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { if len(args) == 0 { - tsValidateError(ts, "gnoland", neg, fmt.Errorf("syntax: gnoland [start|stop]")) + tsValidateError(ts, "gnoland", neg, fmt.Errorf("syntax: gnoland [start|stop|restart]")) return } @@ -168,10 +173,17 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { break } + // parse flags + fs := flag.NewFlagSet("start", flag.ContinueOnError) + nonVal := fs.Bool("non-validator", false, "set up node as a non-validator") + if err := fs.Parse(args); err != nil { + ts.Fatalf("unable to parse `gnoland start` flags: %s", err) + } + // get packages pkgs := ts.Value(envKeyPkgsLoader).(*pkgsLoader) // grab logger creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 - defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) pkgsTxs, err := pkgs.LoadPackages(creator, defaultFee, nil) if err != nil { ts.Fatalf("unable to load packages txs: %s", err) @@ -187,16 +199,50 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // setup genesis state cfg.Genesis.AppState = *genesis + if *nonVal { + // re-create cfg.Genesis.Validators with a throwaway pv, so we start as a + // non-validator. + pv := gnoland.NewMockedPrivValidator() + cfg.Genesis.Validators = []bft.GenesisValidator{ + { + Address: pv.GetPubKey().Address(), + PubKey: pv.GetPubKey(), + Power: 10, + Name: "none", + }, + } + } + cfg.DB = memdb.NewMemDB() // so it can be reused when restarting. n, remoteAddr := TestingInMemoryNode(t, logger, cfg) // Register cleanup - nodes[sid] = &testNode{Node: n} + nodes[sid] = &testNode{Node: n, cfg: cfg} // Add default environments ts.Setenv("RPC_ADDR", remoteAddr) fmt.Fprintln(ts.Stdout(), "node started successfully") + case "restart": + n, ok := nodes[sid] + if !ok { + err = fmt.Errorf("node must be started before being restarted") + break + } + + if stopErr := n.Stop(); stopErr != nil { + err = fmt.Errorf("error stopping node: %w", stopErr) + break + } + + // Create new node with same config. + newNode, newRemoteAddr := TestingInMemoryNode(t, logger, n.cfg) + + // Update testNode and environment variables. + n.Node = newNode + ts.Setenv("RPC_ADDR", newRemoteAddr) + + fmt.Fprintln(ts.Stdout(), "node restarted successfully") case "stop": n, ok := nodes[sid] if !ok { @@ -259,7 +305,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 +385,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") @@ -544,7 +607,7 @@ func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland. return gnoland.Balance{ Address: address, - Amount: std.Coins{std.NewCoin("ugnot", 10e6)}, + Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e6)}, }, nil } @@ -568,19 +631,24 @@ func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic str return gnoland.Balance{ Address: address, - Amount: std.Coins{std.NewCoin("ugnot", 10e6)}, + Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e6)}, }, nil } 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 +656,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 +672,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 06020d9c94b..5e9e2272049 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -3,14 +3,17 @@ package integration import ( "log/slog" "path/filepath" + "slices" "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/require" ) @@ -30,10 +33,19 @@ func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMem err = node.Start() require.NoError(t, err) - select { - case <-node.Ready(): - case <-time.After(time.Second * 10): - require.FailNow(t, "timeout while waiting for the node to start") + ourAddress := config.PrivValidator.GetPubKey().Address() + isValidator := slices.ContainsFunc(config.Genesis.Validators, func(val bft.GenesisValidator) bool { + return val.Address == ourAddress + }) + + // Wait for first block if we are a validator. + // If we are not a validator, we don't produce blocks, so node.Ready() hangs. + if isValidator { + select { + case <-node.Ready(): + case <-time.After(time.Second * 10): + require.FailNow(t, "timeout while waiting for the node to start") + } } return node, node.Config().RPC.ListenAddress @@ -42,7 +54,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 +62,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, @@ -71,10 +83,14 @@ func TestingMinimalNodeConfig(t TestingTS, gnoroot string) *gnoland.InMemoryNode genesis := DefaultTestingGenesisConfig(t, gnoroot, pv.GetPubKey(), tmconfig) return &gnoland.InMemoryNodeConfig{ - PrivValidator: pv, - Genesis: genesis, - TMConfig: tmconfig, - GenesisTxHandler: gnoland.PanicOnFailingTxHandler, + PrivValidator: pv, + Genesis: genesis, + TMConfig: tmconfig, + DB: memdb.NewMemDB(), + InitChainerConfig: gnoland.InitChainerConfig{ + GenesisTxResultHandler: gnoland.PanicOnFailingTxResultHandler, + CacheStdlibLoad: true, + }, } } @@ -102,7 +118,7 @@ func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey Balances: []gnoland.Balance{ { Address: crypto.MustAddressFromString(DefaultAccount_Address), - Amount: std.MustParseCoins("10000000000000ugnot"), + Amount: std.MustParseCoins(ugnot.ValueString(10000000000000)), }, }, Txs: []std.Tx{}, @@ -114,7 +130,7 @@ func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std.Tx { examplesDir := filepath.Join(gnoroot, "examples") - defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee) require.NoError(t, err) diff --git a/gno.land/pkg/keyscli/root.go b/gno.land/pkg/keyscli/root.go index dc5a4f1f9af..19513fc0de6 100644 --- a/gno.land/pkg/keyscli/root.go +++ b/gno.land/pkg/keyscli/root.go @@ -17,7 +17,7 @@ func NewRootCmd(io commands.IO, base client.BaseOptions) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ ShortUsage: " [flags] [...]", - LongHelp: "Manages private keys for the node", + LongHelp: "gno.land keychain & client", Options: []ff.Option{ ff.WithConfigFileFlag("config"), ff.WithConfigFileParser(fftoml.Parser), diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 6dd8050d6b6..43a8fe1fbec 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -38,6 +38,7 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { baseCapKey := store.NewStoreKey("baseCapKey") iavlCapKey := store.NewStoreKey("iavlCapKey") + // Mount db store and iavlstore ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(baseCapKey, dbadapter.StoreConstructor, db) ms.MountStoreWithDB(iavlCapKey, iavl.StoreConstructor, db) @@ -46,11 +47,18 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) acck := authm.NewAccountKeeper(iavlCapKey, std.ProtoBaseAccount) bank := bankm.NewBankKeeper(acck) - stdlibsDir := filepath.Join("..", "..", "..", "..", "gnovm", "stdlibs") - vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir, 100_000_000) + vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, 100_000_000) mcw := ms.MultiCacheWrap() - vmk.Initialize(log.NewNoopLogger(), mcw, cacheStdlibs) + vmk.Initialize(log.NewNoopLogger(), mcw) + stdlibCtx := vmk.MakeGnoTransactionStore(ctx.WithMultiStore(mcw)) + stdlibsDir := filepath.Join("..", "..", "..", "..", "gnovm", "stdlibs") + if cacheStdlibs { + vmk.LoadStdlibCached(stdlibCtx, stdlibsDir) + } else { + vmk.LoadStdlib(stdlibCtx, stdlibsDir) + } + vmk.CommitGnoTransactionStore(stdlibCtx) 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 0020e989eb6..c8d6da98970 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -15,18 +15,22 @@ 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 } + PkgExistError struct{ abciError } + InvalidStmtError struct{ abciError } + InvalidExprError struct{ abciError } + UnauthorizedUserError struct{ abciError } + TypeCheckError struct { abciError 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 PkgExistError) Error() string { return "package already exists" } +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 +38,14 @@ func (e TypeCheckError) Error() string { return bld.String() } +func ErrPkgAlreadyExists(msg string) error { + return errors.Wrap(PkgExistError{}, msg) +} + +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 35706325c20..4171b1cdbc3 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -3,6 +3,7 @@ package vm import ( "testing" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" @@ -26,6 +27,9 @@ func TestAddPkgDeliverTxInsuffGas(t *testing.T) { simulate := false tx.Fee.GasWanted = 3000 gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + // Has to be set up after gas meter in the context; so the stores are + // correctly wrapped in gas stores. + gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) var res sdk.Result abort := false @@ -62,12 +66,15 @@ func TestAddPkgDeliverTx(t *testing.T) { simulate = false tx.Fee.GasWanted = 500000 gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) msgs := tx.GetMsgs() res := vmHandler.Process(gctx, msgs[0]) gasDeliver := gctx.GasMeter().GasConsumed() assert.True(t, res.IsOK()) - assert.Equal(t, int64(91825), gasDeliver) + + // NOTE: let's try to keep this bellow 100_000 :) + assert.Equal(t, int64(92825), gasDeliver) } // Enough gas for a failed transaction. @@ -81,6 +88,7 @@ func TestAddPkgDeliverTxFailed(t *testing.T) { simulate = false tx.Fee.GasWanted = 500000 gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) msgs := tx.GetMsgs() res := vmHandler.Process(gctx, msgs[0]) gasDeliver := gctx.GasMeter().GasConsumed() @@ -100,6 +108,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { simulate = false tx.Fee.GasWanted = 2230 gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) var res sdk.Result abort := false @@ -126,7 +135,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { res = vmHandler.Process(gctx, msgs[0]) } -// Set up a test env for both a successful and a failed tx +// Set up a test env for both a successful and a failed tx. func setupAddPkg(success bool) (sdk.Context, sdk.Tx, vmHandler) { // setup env := setupTestEnv() @@ -138,7 +147,7 @@ func setupAddPkg(success bool) (sdk.Context, sdk.Tx, vmHandler) { addr := crypto.AddressFromPreimage([]byte("test1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(ugnot.ValueString(10000000))) // success message var files []*std.MemFile if success { @@ -170,7 +179,7 @@ func Echo() UnknowType { // create messages and a transaction msg := NewMsgAddPackage(addr, pkgPath, files) msgs := []std.Msg{msg} - fee := std.NewFee(500000, std.MustParseCoin("1ugnot")) + fee := std.NewFee(500000, std.MustParseCoin(ugnot.ValueString(1))) tx := std.NewTx(msgs, fee, []std.Signature{}, "") return ctx, tx, vmHandler diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 78786cf0b4d..365473b3e7a 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,12 +9,14 @@ import ( "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" @@ -32,8 +34,8 @@ import ( ) const ( - maxAllocTx = 500 * 1000 * 1000 - maxAllocQuery = 1500 * 1000 * 1000 // higher limit for queries + maxAllocTx = 500_000_000 + maxAllocQuery = 1_500_000_000 // higher limit for queries ) // vm.VMKeeperI defines a module interface that supports Gno @@ -43,17 +45,20 @@ type VMKeeperI interface { 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) + LoadStdlib(ctx sdk.Context, stdlibDir string) + LoadStdlibCached(ctx sdk.Context, stdlibDir string) + MakeGnoTransactionStore(ctx sdk.Context) sdk.Context + CommitGnoTransactionStore(ctx sdk.Context) } var _ VMKeeperI = &VMKeeper{} // VMKeeper holds all package code and store state. type VMKeeper struct { - baseKey store.StoreKey - iavlKey store.StoreKey - acck auth.AccountKeeper - bank bank.BankKeeper - stdlibsDir string + baseKey store.StoreKey + iavlKey store.StoreKey + acck auth.AccountKeeper + bank bank.BankKeeper // cached, the DeliverTx persistent state. gnoStore gno.Store @@ -67,17 +72,15 @@ func NewVMKeeper( iavlKey store.StoreKey, acck auth.AccountKeeper, bank bank.BankKeeper, - stdlibsDir string, maxCycles int64, ) *VMKeeper { // TODO: create an Options struct to avoid too many constructor parameters vmk := &VMKeeper{ - baseKey: baseKey, - iavlKey: iavlKey, - acck: acck, - bank: bank, - stdlibsDir: stdlibsDir, - maxCycles: maxCycles, + baseKey: baseKey, + iavlKey: iavlKey, + acck: acck, + bank: bank, + maxCycles: maxCycles, } return vmk } @@ -85,40 +88,18 @@ func NewVMKeeper( func (vm *VMKeeper) Initialize( logger *slog.Logger, ms store.MultiStore, - cacheStdlibLoad bool, ) { if vm.gnoStore != nil { panic("should not happen") } - baseSDKStore := ms.GetStore(vm.baseKey) - iavlSDKStore := ms.GetStore(vm.iavlKey) + baseStore := ms.GetStore(vm.baseKey) + iavlStore := ms.GetStore(vm.iavlKey) - 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) + vm.gnoStore = gno.NewStore(alloc, baseStore, iavlStore) + vm.gnoStore.SetNativeStore(stdlibs.NativeStore) - logger.Debug("Standard libraries initialized", - "elapsed", time.Since(start)) - } else { + if vm.gnoStore.NumMemPackages() > 0 { // 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. @@ -128,7 +109,7 @@ func uncachedPackageLoad( gno.MachineOptions{ PkgPath: "", Output: os.Stdout, // XXX - Store: gnoStore, + Store: vm.gnoStore, }) defer m2.Release() gno.DisableDebug() @@ -138,46 +119,52 @@ func uncachedPackageLoad( logger.Debug("GnoVM packages preprocessed", "elapsed", time.Since(start)) } - return gnoStore } -func cachedStdlibLoad(stdlibsDir string, baseStore, iavlStore store.Store) gno.Store { +type stdlibCache struct { + dir string + base store.Store + iavl store.Store + gno gno.Store +} + +var ( + cachedStdlibOnce sync.Once + cachedStdlib stdlibCache +) + +// LoadStdlib loads the Gno standard library into the given store. +func (vm *VMKeeper) LoadStdlibCached(ctx sdk.Context, stdlibDir string) { 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) - }) + cachedStdlib = stdlibCache{ + dir: stdlibDir, + base: dbadapter.StoreConstructor(memdb.NewMemDB(), types.StoreOptions{}), + iavl: dbadapter.StoreConstructor(memdb.NewMemDB(), types.StoreOptions{}), + } - itr := cachedStdlibBase.Iterator(nil, nil) - for ; itr.Valid(); itr.Next() { - baseStore.Set(itr.Key(), itr.Value()) - } + gs := gno.NewStore(nil, cachedStdlib.base, cachedStdlib.iavl) + gs.SetNativeStore(stdlibs.NativeStore) + loadStdlib(gs, stdlibDir) + cachedStdlib.gno = gs + }) - itr = cachedStdlibIavl.Iterator(nil, nil) - for ; itr.Valid(); itr.Next() { - iavlStore.Set(itr.Key(), itr.Value()) + if stdlibDir != cachedStdlib.dir { + panic(fmt.Sprintf( + "cannot load cached stdlib: cached stdlib is in dir %q; wanted to load stdlib in dir %q", + cachedStdlib.dir, stdlibDir)) } - alloc := gno.NewAllocator(maxAllocTx) - gs := gno.NewStore(alloc, baseStore, iavlStore) - gs.SetNativeStore(stdlibs.NativeStore) - gno.CopyCachesFromStore(gs, cachedGnoStore) - return gs + gs := vm.getGnoTransactionStore(ctx) + gno.CopyFromCachedStore(gs, cachedStdlib.gno, cachedStdlib.base, cachedStdlib.iavl) } -var ( - cachedStdlibOnce sync.Once - cachedStdlibBase *memdb.MemDB - cachedStdlibIavl *memdb.MemDB - cachedGnoStore gno.Store -) +// LoadStdlib loads the Gno standard library into the given store. +func (vm *VMKeeper) LoadStdlib(ctx sdk.Context, stdlibDir string) { + gs := vm.getGnoTransactionStore(ctx) + loadStdlib(gs, stdlibDir) +} -func loadStdlib(stdlibsDir string, store gno.Store) { +func loadStdlib(store gno.Store, stdlibDir string) { stdlibInitList := stdlibs.InitOrder() for _, lib := range stdlibInitList { if lib == "testing" { @@ -185,12 +172,12 @@ func loadStdlib(stdlibsDir string, store gno.Store) { // like fmt and encoding/json continue } - loadStdlibPackage(lib, stdlibsDir, store) + loadStdlibPackage(lib, stdlibDir, store) } } -func loadStdlibPackage(pkgPath, stdlibsDir string, store gno.Store) { - stdlibPath := filepath.Join(stdlibsDir, pkgPath) +func loadStdlibPackage(pkgPath, stdlibDir string, store gno.Store) { + stdlibPath := filepath.Join(stdlibDir, pkgPath) if !osm.DirExists(stdlibPath) { // does not exist. panic(fmt.Sprintf("failed loading stdlib %q: does not exist", pkgPath)) @@ -211,40 +198,111 @@ func loadStdlibPackage(pkgPath, stdlibsDir string, store gno.Store) { m.RunMemPackage(memPkg, true) } -func (vm *VMKeeper) getGnoStore(ctx sdk.Context) gno.Store { - // construct main store if nil. - if vm.gnoStore == nil { - panic("VMKeeper must first be initialized") - } - switch ctx.Mode() { - case sdk.RunTxModeDeliver: - // swap sdk store of existing store. - // this is needed due to e.g. gas wrappers. - baseSDKStore := ctx.Store(vm.baseKey) - iavlSDKStore := ctx.Store(vm.iavlKey) - vm.gnoStore.SwapStores(baseSDKStore, iavlSDKStore) - // clear object cache for every transaction. - // NOTE: this is inefficient, but simple. - // in the future, replace with more advanced caching strategy. - vm.gnoStore.ClearObjectCache() - return vm.gnoStore - case sdk.RunTxModeCheck: - // For query??? XXX Why not RunTxModeQuery? - simStore := vm.gnoStore.Fork() - baseSDKStore := ctx.Store(vm.baseKey) - iavlSDKStore := ctx.Store(vm.iavlKey) - simStore.SwapStores(baseSDKStore, iavlSDKStore) - return simStore - case sdk.RunTxModeSimulate: - // always make a new store for simulate for isolation. - simStore := vm.gnoStore.Fork() - baseSDKStore := ctx.Store(vm.baseKey) - iavlSDKStore := ctx.Store(vm.iavlKey) - simStore.SwapStores(baseSDKStore, iavlSDKStore) - return simStore +type gnoStoreContextKeyType struct{} + +var gnoStoreContextKey gnoStoreContextKeyType + +func (vm *VMKeeper) newGnoTransactionStore(ctx sdk.Context) gno.TransactionStore { + base := ctx.Store(vm.baseKey) + iavl := ctx.Store(vm.iavlKey) + + return vm.gnoStore.BeginTransaction(base, iavl) +} + +func (vm *VMKeeper) MakeGnoTransactionStore(ctx sdk.Context) sdk.Context { + return ctx.WithValue(gnoStoreContextKey, vm.newGnoTransactionStore(ctx)) +} + +func (vm *VMKeeper) CommitGnoTransactionStore(ctx sdk.Context) { + vm.getGnoTransactionStore(ctx).Write() +} + +func (vm *VMKeeper) getGnoTransactionStore(ctx sdk.Context) gno.TransactionStore { + txStore := ctx.Value(gnoStoreContextKey).(gno.TransactionStore) + txStore.ClearObjectCache() + return txStore +} + +// 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.getGnoTransactionStore(ctx) + + match := reNamespace.FindStringSubmatch(pkgPath) + switch len(match) { + case 0: + return ErrInvalidPkgPath(pkgPath) // no match + case 2: // ok default: - panic("should not happen") + 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. @@ -253,7 +311,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { pkgPath := msg.Package.Path memPkg := msg.Package deposit := msg.Deposit - gnostore := vm.getGnoStore(ctx) + gnostore := vm.getGnoTransactionStore(ctx) // Validate arguments. if creator.IsZero() { @@ -267,14 +325,15 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { return ErrInvalidPkgPath(err.Error()) } if pv := gnostore.GetPackage(pkgPath, false); pv != nil { - return ErrInvalidPkgPath("package already exists: " + pkgPath) + return ErrPkgAlreadyExists("package already exists: " + 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) } @@ -284,9 +343,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 { @@ -349,7 +408,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { pkgPath := msg.PkgPath // to import fnc := msg.Func - gnostore := vm.getGnoStore(ctx) + gnostore := vm.getGnoTransactionStore(ctx) // Get the package and function type. pv := gnostore.GetPackage(pkgPath, false) pl := gno.PackageNodeLocation(pkgPath) @@ -422,12 +481,15 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { m.SetActivePackage(mpv) defer func() { if r := recover(); r != nil { - switch r.(type) { + switch r := r.(type) { case store.OutOfGasException: // panic in consumeGas() panic(r) + case gno.UnhandledPanicError: + err = errors.Wrap(fmt.Errorf("%v", r.Error()), "VM call panic: %s\nStacktrace: %s\n", + r.Error(), m.ExceptionsStacktrace()) default: - err = errors.Wrap(fmt.Errorf("%v", r), "VM call panic: %v\n%s\n", - r, m.String()) + err = errors.Wrap(fmt.Errorf("%v", r), "VM call panic: %v\nMachine State:%s\nStacktrace: %s\n", + r, m.String(), m.Stacktrace().String()) return } } @@ -460,7 +522,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { caller := msg.Caller pkgAddr := caller - gnostore := vm.getGnoStore(ctx) + gnostore := vm.getGnoTransactionStore(ctx) send := msg.Send memPkg := msg.Package @@ -479,7 +541,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) } @@ -573,7 +636,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // QueryFuncs returns public facing function signatures. func (vm *VMKeeper) QueryFuncs(ctx sdk.Context, pkgPath string) (fsigs FunctionSignatures, err error) { - store := vm.getGnoStore(ctx) + store := vm.newGnoTransactionStore(ctx) // throwaway (never committed) // Ensure pkgPath is realm. if !gno.IsRealmPath(pkgPath) { err = ErrInvalidPkgPath(fmt.Sprintf( @@ -636,7 +699,7 @@ func (vm *VMKeeper) QueryFuncs(ctx sdk.Context, pkgPath string) (fsigs FunctionS // TODO: then, rename to "Eval". func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res string, err error) { alloc := gno.NewAllocator(maxAllocQuery) - gnostore := vm.getGnoStore(ctx) + gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) pkgAddr := gno.DerivePkgAddr(pkgPath) // Get Package. pv := gnostore.GetPackage(pkgPath, false) @@ -703,7 +766,7 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res // TODO: then, rename to "EvalString". func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string) (res string, err error) { alloc := gno.NewAllocator(maxAllocQuery) - gnostore := vm.getGnoStore(ctx) + gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) pkgAddr := gno.DerivePkgAddr(pkgPath) // Get Package. pv := gnostore.GetPackage(pkgPath, false) @@ -764,7 +827,7 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string } func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err error) { - store := vm.getGnoStore(ctx) + store := vm.newGnoTransactionStore(ctx) // throwaway (never committed) dirpath, filename := std.SplitFilepath(filepath) if filename != "" { memFile := store.GetMemFile(dirpath, filename) diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index 7210e42e6be..9257da2ddaf 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -9,59 +9,75 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/store/dbadapter" + "github.com/gnolang/gno/tm2/pkg/store/types" ) +var coinsString = ugnot.ValueString(10000000) + func TestVMKeeperAddPackage(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ { Name: "test.gno", Body: `package test - -func Echo() string { - return "hello world" -}`, +func Echo() string {return "hello world"}`, }, } pkgPath := "gno.land/r/test" msg1 := NewMsgAddPackage(addr, pkgPath, files) - assert.Nil(t, env.vmk.gnoStore.GetPackage(pkgPath, false)) + assert.Nil(t, env.vmk.getGnoTransactionStore(ctx).GetPackage(pkgPath, false)) err := env.vmk.AddPackage(ctx, msg1) assert.NoError(t, err) - assert.NotNil(t, env.vmk.gnoStore.GetPackage(pkgPath, false)) + assert.NotNil(t, env.vmk.getGnoTransactionStore(ctx).GetPackage(pkgPath, false)) err = env.vmk.AddPackage(ctx, msg1) assert.Error(t, err) - assert.True(t, errors.Is(err, InvalidPkgPathError{})) + assert.True(t, errors.Is(err, PkgExistError{})) + + // added package is formatted + store := env.vmk.getGnoTransactionStore(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. func TestVMKeeperOrigSend1(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -88,7 +104,7 @@ func Echo(msg string) string { assert.NoError(t, err) // Run Echo function. - coins := std.MustParseCoins("10000000ugnot") + coins := std.MustParseCoins(coinsString) msg2 := NewMsgCall(addr, coins, pkgPath, "Echo", []string{"hello world"}) res, err := env.vmk.Call(ctx, msg2) assert.NoError(t, err) @@ -99,14 +115,14 @@ func Echo(msg string) string { // Sending too much fails func TestVMKeeperOrigSend2(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -141,7 +157,7 @@ func GetAdmin() string { assert.NoError(t, err) // Run Echo function. - coins := std.MustParseCoins("11000000ugnot") + coins := std.MustParseCoins(ugnot.ValueString(11000000)) msg2 := NewMsgCall(addr, coins, pkgPath, "Echo", []string{"hello world"}) res, err := env.vmk.Call(ctx, msg2) assert.Error(t, err) @@ -153,14 +169,14 @@ func GetAdmin() string { // Sending more than tx send fails. func TestVMKeeperOrigSend3(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -187,7 +203,7 @@ func Echo(msg string) string { assert.NoError(t, err) // Run Echo function. - coins := std.MustParseCoins("9000000ugnot") + coins := std.MustParseCoins(ugnot.ValueString(9000000)) msg2 := NewMsgCall(addr, coins, pkgPath, "Echo", []string{"hello world"}) // XXX change this into an error and make sure error message is descriptive. _, err = env.vmk.Call(ctx, msg2) @@ -197,14 +213,14 @@ func Echo(msg string) string { // Sending realm package coins succeeds. func TestVMKeeperRealmSend1(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -231,7 +247,7 @@ func Echo(msg string) string { assert.NoError(t, err) // Run Echo function. - coins := std.MustParseCoins("10000000ugnot") + coins := std.MustParseCoins(coinsString) msg2 := NewMsgCall(addr, coins, pkgPath, "Echo", []string{"hello world"}) res, err := env.vmk.Call(ctx, msg2) assert.NoError(t, err) @@ -241,14 +257,14 @@ func Echo(msg string) string { // Sending too much realm package coins fails. func TestVMKeeperRealmSend2(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -275,7 +291,7 @@ func Echo(msg string) string { assert.NoError(t, err) // Run Echo function. - coins := std.MustParseCoins("9000000ugnot") + coins := std.MustParseCoins(ugnot.ValueString(9000000)) msg2 := NewMsgCall(addr, coins, pkgPath, "Echo", []string{"hello world"}) // XXX change this into an error and make sure error message is descriptive. _, err = env.vmk.Call(ctx, msg2) @@ -285,14 +301,14 @@ func Echo(msg string) string { // Assign admin as OrigCaller on deploying the package. func TestVMKeeperOrigCallerInit(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -339,7 +355,7 @@ func GetAdmin() string { // Call Run without imports, without variables. func TestVMKeeperRunSimple(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) @@ -378,7 +394,7 @@ func TestVMKeeperRunImportStdlibsColdStdlibLoad(t *testing.T) { func testVMKeeperRunImportStdlibs(t *testing.T, env testEnv) { t.Helper() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) @@ -408,14 +424,14 @@ func main() { func TestNumberOfArgsError(t *testing.T) { env := setupTestEnv() - ctx := env.ctx + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) // Give "addr1" some gnots. addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*std.MemFile{ @@ -434,7 +450,7 @@ func Echo(msg string) string { assert.NoError(t, err) // Call Echo function with wrong number of arguments - coins := std.MustParseCoins("1ugnot") + coins := std.MustParseCoins(ugnot.ValueString(1)) msg2 := NewMsgCall(addr, coins, pkgPath, "Echo", []string{"hello world", "extra arg"}) assert.PanicsWithValue( t, @@ -444,3 +460,59 @@ func Echo(msg string) string { }, ) } + +func TestVMKeeperReinitialize(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + + // Give "addr1" some gnots. + addr := crypto.AddressFromPreimage([]byte("addr1")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + env.acck.SetAccount(ctx, acc) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + + // Create test package. + files := []*std.MemFile{ + {"init.gno", ` +package test + +func Echo(msg string) string { + return "echo:"+msg +}`}, + } + pkgPath := "gno.land/r/test" + msg1 := NewMsgAddPackage(addr, pkgPath, files) + err := env.vmk.AddPackage(ctx, msg1) + require.NoError(t, err) + + // Run Echo function. + msg2 := NewMsgCall(addr, nil, pkgPath, "Echo", []string{"hello world"}) + res, err := env.vmk.Call(ctx, msg2) + require.NoError(t, err) + assert.Equal(t, `("echo:hello world" string)`+"\n\n", res) + + // Clear out gnovm and reinitialize. + env.vmk.gnoStore = nil + mcw := env.ctx.MultiStore().MultiCacheWrap() + env.vmk.Initialize(log.NewNoopLogger(), mcw) + mcw.MultiWrite() + + // Run echo again, and it should still work. + res, err = env.vmk.Call(ctx, msg2) + require.NoError(t, err) + assert.Equal(t, `("echo:hello world" string)`+"\n\n", res) +} + +func Test_loadStdlibPackage(t *testing.T) { + mdb := memdb.NewMemDB() + cs := dbadapter.StoreConstructor(mdb, types.StoreOptions{}) + + gs := gnolang.NewStore(nil, cs, cs) + assert.PanicsWithValue(t, `failed loading stdlib "notfound": does not exist`, func() { + loadStdlibPackage("notfound", "./testdata", gs) + }) + assert.PanicsWithValue(t, `failed loading stdlib "emptystdlib": not a valid MemPackage`, func() { + loadStdlibPackage("emptystdlib", "./testdata", gs) + }) +} diff --git a/gno.land/pkg/sdk/vm/msgs.go b/gno.land/pkg/sdk/vm/msgs.go index e5616fa2395..d650c23f382 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(msg.Deposit.String()) } // XXX validate files. return nil diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index b2e7fbecfc4..30dd116d4e3 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -18,7 +18,9 @@ var Package = amino.RegisterPackage(amino.NewPackage( // errors InvalidPkgPathError{}, "InvalidPkgPathError", + PkgExistError{}, "PkgExistError", InvalidStmtError{}, "InvalidStmtError", InvalidExprError{}, "InvalidExprError", TypeCheckError{}, "TypeCheckError", + UnauthorizedUserError{}, "UnauthorizedUserError", )) diff --git a/gno.land/pkg/sdk/vm/testdata/emptystdlib/README b/gno.land/pkg/sdk/vm/testdata/emptystdlib/README new file mode 100644 index 00000000000..e4454ed67f8 --- /dev/null +++ b/gno.land/pkg/sdk/vm/testdata/emptystdlib/README @@ -0,0 +1 @@ +see keeper_test.go diff --git a/gno.land/pkg/sdk/vm/vm.proto b/gno.land/pkg/sdk/vm/vm.proto index aa0be4f6e14..e02226e1ae4 100644 --- a/gno.land/pkg/sdk/vm/vm.proto +++ b/gno.land/pkg/sdk/vm/vm.proto @@ -30,6 +30,9 @@ message m_addpkg { message InvalidPkgPathError { } +message PkgExistError { +} + message InvalidStmtError { } diff --git a/gnovm/Makefile b/gnovm/Makefile index 6e939289fb8..5ff3af9c253 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -53,7 +53,7 @@ lint: $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint run --config ../.github/golangci.yml ./... .PHONY: fmt -fmt: +fmt: go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go index 8d4661e3de8..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 @@ -65,7 +66,7 @@ func newBugCmd(io commands.IO) *commands.Command { The new issue body is prefilled for you with the following information: -- Go version (example: go1.22.2) +- Go version (example: go1.22.4) - OS and CPU architecture (example: linux/amd64) - Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) 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 index 7c5ad42c2b0..de6c28c4df0 100644 --- a/gnovm/cmd/gno/fmt.go +++ b/gnovm/cmd/gno/fmt.go @@ -162,7 +162,7 @@ func fmtProcessSingleFile(cfg *fmtCfg, file string, processFile fmtProcessFileFu } if !cfg.write { if !cfg.diff && !cfg.quiet { - io.Println(string(out)) + io.Out().Write(out) } return true } 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 39306d59cb0..cfbfe995a46 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -188,8 +188,14 @@ func listNonTestFiles(dir string) ([]string, error) { func runExpr(m *gno.Machine, expr string) { defer func() { if r := recover(); r != nil { - fmt.Printf("panic running expression %s: %v\n%s\n", - expr, r, m.String()) + switch r := r.(type) { + case gno.UnhandledPanicError: + fmt.Printf("panic running expression %s: %v\nStacktrace: %s\n", + expr, r.Error(), m.ExceptionsStacktrace()) + default: + fmt.Printf("panic running expression %s: %v\nMachine State:%s\nStacktrace: %s\n", + expr, r, m.String(), m.Stacktrace().String()) + } panic(r) } }() diff --git a/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar b/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar index 14f85227335..97d367f6fdb 100644 --- a/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar +++ b/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar @@ -6,5 +6,4 @@ cmp stderr stderr.golden 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 index bc186f98b6f..3d50f4e6428 100644 --- a/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar +++ b/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar @@ -26,5 +26,4 @@ var rand = &S{} 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 index 92767983705..1cfedd06ad4 100644 --- a/gnovm/cmd/gno/testdata/gno_fmt/include.txtar +++ b/gnovm/cmd/gno/testdata/gno_fmt/include.txtar @@ -27,5 +27,4 @@ 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 index 9e16f33591d..4002902bc59 100644 --- a/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar +++ b/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar @@ -92,5 +92,4 @@ func myBlog() *blog.Blog { 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 index 56fca6ddbd7..6470fe76297 100644 --- a/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar +++ b/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar @@ -35,5 +35,4 @@ import ( ) var yes = rand.Val - -- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar b/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar index 50d0970497e..19ad27b2f1a 100644 --- a/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar +++ b/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar @@ -49,5 +49,4 @@ import ( func main() { println("a", v1.Get("a")) } - -- stderr.golden -- diff --git a/gnovm/pkg/gnolang/alloc.go b/gnovm/pkg/gnolang/alloc.go index 6fef5eda834..df042038e43 100644 --- a/gnovm/pkg/gnolang/alloc.go +++ b/gnovm/pkg/gnolang/alloc.go @@ -223,7 +223,12 @@ func (alloc *Allocator) NewSlice(base Value, offset, length, maxcap int) *SliceV } } -// NOTE: also allocates the underlying array from list. +// NewSliceFromList allocates a new slice with the underlying array value +// populated from `list`. This should not be called from areas in the codebase +// that are doing allocations with potentially large user provided values, e.g. +// `make()` and `append()`. Using `Alloc.NewListArray` can be used is most cases +// to allocate the space for the `TypedValue` list before doing the allocation +// in the go runtime -- see the `make()` code in uverse.go. func (alloc *Allocator) NewSliceFromList(list []TypedValue) *SliceValue { alloc.AllocateSlice() alloc.AllocateListArray(int64(cap(list))) @@ -238,7 +243,9 @@ func (alloc *Allocator) NewSliceFromList(list []TypedValue) *SliceValue { } } -// NOTE: also allocates the underlying array from data. +// NewSliceFromData allocates a new slice with the underlying data array +// value populated from `data`. See the doc for `NewSliceFromList` for +// correct usage notes. func (alloc *Allocator) NewSliceFromData(data []byte) *SliceValue { alloc.AllocateSlice() alloc.AllocateDataArray(int64(cap(data))) diff --git a/gnovm/pkg/gnolang/debugger.go b/gnovm/pkg/gnolang/debugger.go index 284120ef6c5..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 { @@ -484,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 { diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 10d7c5ce250..fe059ba9f56 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -24,7 +24,7 @@ type writeNopCloser struct{ io.Writer } func (writeNopCloser) Close() error { return nil } // TODO (Marc): move evalTest to gnovm/tests package and remove code duplicates -func evalTest(debugAddr, in, file string) (out, err string) { +func evalTest(debugAddr, in, file string) (out, err, stacktrace string) { bout := bytes.NewBufferString("") berr := bytes.NewBufferString("") stdin := bytes.NewBufferString(in) @@ -40,7 +40,7 @@ func evalTest(debugAddr, in, file string) (out, err string) { if r := recover(); r != nil { err = fmt.Sprintf("%v", r) } - out = strings.TrimSpace(out) + out = strings.TrimSuffix(out, "\n") err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() @@ -58,6 +58,18 @@ func evalTest(debugAddr, in, file string) (out, err string) { }) defer m.Release() + defer func() { + if r := recover(); r != nil { + switch r.(type) { + case gnolang.UnhandledPanicError: + stacktrace = m.ExceptionsStacktrace() + default: + stacktrace = m.Stacktrace().String() + } + stacktrace = strings.TrimSpace(strings.ReplaceAll(stacktrace, "../../tests/files/", "files/")) + panic(r) + } + }() if debugAddr != "" { if e := m.Debugger.Serve(debugAddr); e != nil { @@ -69,7 +81,7 @@ func evalTest(debugAddr, in, file string) (out, err string) { m.RunFiles(f) ex, _ := gnolang.ParseExpr("main()") m.Eval(ex) - out, err = bout.String(), berr.String() + out, err, stacktrace = bout.String(), berr.String(), m.ExceptionsStacktrace() return } @@ -78,7 +90,7 @@ func runDebugTest(t *testing.T, targetPath string, tests []dtest) { for _, test := range tests { t.Run("", func(t *testing.T) { - out, err := evalTest("", 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("unexpected output\nwant\"%s\"\n got \"%s\"", test.out, out) @@ -194,7 +206,7 @@ func TestRemoteDebug(t *testing.T) { } func TestRemoteError(t *testing.T) { - _, err := evalTest(":xxx", "", debugTarget) + _, 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") { diff --git a/gnovm/pkg/gnolang/eval_test.go b/gnovm/pkg/gnolang/eval_test.go index fdd8e0204d1..ba93dd00396 100644 --- a/gnovm/pkg/gnolang/eval_test.go +++ b/gnovm/pkg/gnolang/eval_test.go @@ -1,62 +1,121 @@ package gnolang_test import ( + "io/fs" "os" - "path" + "path/filepath" + "regexp" + "sort" "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) + err := fs.WalkDir(os.DirFS(dir), ".", func(path string, de fs.DirEntry, err error) error { + switch { + case err != nil: + return err + case path == "extern": + return fs.SkipDir + case de.IsDir(): + return nil + } + + fullPath := filepath.Join(dir, path) + wantOut, wantErr, wantStacktrace, ok := testData(fullPath) if !ok { - continue + return nil } - t.Run(f.Name(), func(t *testing.T) { - out, err := evalTest("", "", path.Join(dir, f.Name())) + + t.Run(path, func(t *testing.T) { + out, err, stacktrace := evalTest("", "", fullPath) if wantErr != "" && !strings.Contains(err, wantErr) || wantErr == "" && err != "" { t.Fatalf("unexpected error\nWant: %s\n Got: %s", wantErr, err) } + + if wantStacktrace != "" && !strings.Contains(stacktrace, wantStacktrace) { + t.Fatalf("unexpected stacktrace\nWant: %s\n Got: %s", wantStacktrace, stacktrace) + } if wantOut != "" && out != wantOut { t.Fatalf("unexpected output\nWant: %s\n Got: %s", wantOut, out) } }) + + return nil + }) + if err != nil { + t.Fatal(err) } } // 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()) +func testData(name string) (testOut, testErr, testStacktrace string, ok bool) { if !strings.HasSuffix(name, ".gno") || strings.HasSuffix(name, "_long.gno") { - return "", "", false + return } buf, err := os.ReadFile(name) if err != nil { - return "", "", false + return } str := string(buf) if strings.Contains(str, "// PKGPATH:") { - return "", "", false + return } - return commentFrom(str, "\n// Output:"), commentFrom(str, "\n// Error:"), true + res := commentFrom(str, []string{ + "// Output:", + "// Error:", + "// Stacktrace:", + }) + + return res[0], res[1], res[2], 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 "" +type directive struct { + delim string + res string + index int +} + +// (?m) makes ^ and $ match start/end of string +var reCommentPrefix = regexp.MustCompile("(?m)^//(?: |$)") + +// commentFrom returns the comments from s that are between the delimiters. +func commentFrom(s string, delims []string) []string { + directives := make([]directive, len(delims)) + directivesFound := make([]*directive, 0, len(delims)) + + for i, delim := range delims { + // must find delim isolated on one line + delim = "\n" + delim + "\n" + index := strings.Index(s, delim) + directives[i] = directive{delim: delim, index: index} + if index >= 0 { + directivesFound = append(directivesFound, &directives[i]) + } + } + sort.Slice(directivesFound, func(i, j int) bool { + return directivesFound[i].index < directivesFound[j].index + }) + + for i := range directivesFound { + next := len(s) + if i != len(directivesFound)-1 { + next = directivesFound[i+1].index + } + + parsed := reCommentPrefix.ReplaceAllLiteralString( + s[directivesFound[i].index+len(directivesFound[i].delim):next], + "") + directivesFound[i].res = strings.TrimSuffix(parsed, "\n") } - return strings.TrimSpace(strings.ReplaceAll(s[index+len(delim):], "\n// ", "\n")) + + res := make([]string, len(directives)) + for i, d := range directives { + res[i] = d.res + } + + return res } diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index c808fc111b0..2ac1027eb32 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -2,8 +2,11 @@ package gnolang import ( "fmt" + "strings" ) +const maxStacktraceSize = 128 + //---------------------------------------- // (runtime) Frame @@ -64,6 +67,10 @@ func (fr Frame) String() string { } } +func (fr *Frame) IsCall() bool { + return fr.Func != nil || fr.GoFunc != nil +} + func (fr *Frame) PushDefer(dfr Defer) { fr.Defers = append(fr.Defers, dfr) } @@ -92,3 +99,119 @@ type Defer struct { // a panic occurs and is decremented each time a panic is recovered. PanicScope uint } + +type StacktraceCall struct { + Stmt Stmt + Frame *Frame +} +type Stacktrace struct { + Calls []StacktraceCall + NumFramesElided int +} + +func (s Stacktrace) String() string { + var builder strings.Builder + + for i := 0; i < len(s.Calls); i++ { + if s.NumFramesElided > 0 && i == maxStacktraceSize/2 { + fmt.Fprintf(&builder, "...%d frame(s) elided...\n", s.NumFramesElided) + } + + call := s.Calls[i] + cx := call.Frame.Source.(*CallExpr) + switch { + case call.Frame.Func != nil && call.Frame.Func.IsNative(): + fmt.Fprintf(&builder, "%s\n", toExprTrace(cx)) + fmt.Fprintf(&builder, " gonative:%s.%s\n", call.Frame.Func.NativePkg, call.Frame.Func.NativeName) + case call.Frame.Func != nil: + fmt.Fprintf(&builder, "%s\n", toExprTrace(cx)) + fmt.Fprintf(&builder, " %s/%s:%d\n", call.Frame.Func.PkgPath, call.Frame.Func.FileName, call.Stmt.GetLine()) + case call.Frame.GoFunc != nil: + fmt.Fprintf(&builder, "%s\n", toExprTrace(cx)) + fmt.Fprintf(&builder, " gofunction:%s\n", call.Frame.GoFunc.Value.Type()) + default: + panic("StacktraceCall has a non-call Frame") + } + } + return builder.String() +} + +func toExprTrace(ex Expr) string { + switch ex := ex.(type) { + case *CallExpr: + s := make([]string, len(ex.Args)) + for i, arg := range ex.Args { + s[i] = toExprTrace(arg) + } + return fmt.Sprintf("%s(%s)", toExprTrace(ex.Func), strings.Join(s, ",")) + case *BinaryExpr: + return fmt.Sprintf("%s %s %s", toExprTrace(ex.Left), ex.Op.TokenString(), toExprTrace(ex.Right)) + case *UnaryExpr: + return fmt.Sprintf("%s%s", ex.Op.TokenString(), toExprTrace(ex.X)) + case *SelectorExpr: + return fmt.Sprintf("%s.%s", toExprTrace(ex.X), ex.Sel) + case *IndexExpr: + return fmt.Sprintf("%s[%s]", toExprTrace(ex.X), toExprTrace(ex.Index)) + case *StarExpr: + return fmt.Sprintf("*%s", toExprTrace(ex.X)) + case *RefExpr: + return fmt.Sprintf("&%s", toExprTrace(ex.X)) + case *CompositeLitExpr: + lenEl := len(ex.Elts) + if ex.Type == nil { + return fmt.Sprintf("", lenEl) + } + + return fmt.Sprintf("%s", toExprTrace(ex.Type), lenEl) + case *FuncLitExpr: + return fmt.Sprintf("%s{ ... }", toExprTrace(&ex.Type)) + case *TypeAssertExpr: + return fmt.Sprintf("%s.(%s)", toExprTrace(ex.X), toExprTrace(ex.Type)) + case *ConstExpr: + return toConstExpTrace(ex) + case *NameExpr, *BasicLitExpr, *SliceExpr: + return ex.String() + } + + return ex.String() +} + +func toConstExpTrace(cte *ConstExpr) string { + tv := cte.TypedValue + + switch bt := baseOf(tv.T).(type) { + case PrimitiveType: + switch bt { + case UntypedBoolType, BoolType: + return fmt.Sprintf("%t", tv.GetBool()) + case UntypedStringType, StringType: + return tv.GetString() + case IntType: + return fmt.Sprintf("%d", tv.GetInt()) + case Int8Type: + return fmt.Sprintf("%d", tv.GetInt8()) + case Int16Type: + return fmt.Sprintf("%d", tv.GetInt16()) + case UntypedRuneType, Int32Type: + return fmt.Sprintf("%d", tv.GetInt32()) + case Int64Type: + return fmt.Sprintf("%d", tv.GetInt64()) + case UintType: + return fmt.Sprintf("%d", tv.GetUint()) + case Uint8Type: + return fmt.Sprintf("%d", tv.GetUint8()) + case Uint16Type: + return fmt.Sprintf("%d", tv.GetUint16()) + case Uint32Type: + return fmt.Sprintf("%d", tv.GetUint32()) + case Uint64Type: + return fmt.Sprintf("%d", tv.GetUint64()) + case Float32Type: + return fmt.Sprintf("%v", tv.GetFloat32()) + case Float64Type: + return fmt.Sprintf("%v", tv.GetFloat64()) + } + } + + return tv.T.String() +} diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 54d808faefc..1f83303023c 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -179,7 +179,7 @@ func BenchmarkIfStatement(b *testing.B) { func main() { for i:=0; i<10000; i++ { if i > 10 { - + } } }` @@ -356,6 +356,7 @@ func BenchmarkPreprocess(b *testing.B) { Inc("i"), ), )) + pn := NewPackageNode("hey", "gno.land/p/hey", nil) copies := make([]*FuncDecl, b.N) for i := 0; i < b.N; i++ { copies[i] = main.Copy().(*FuncDecl) @@ -363,6 +364,8 @@ func BenchmarkPreprocess(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { + // initStaticBlocks is always performed before a Preprocess + initStaticBlocks(nil, pn, copies[i]) main = Preprocess(nil, pkg, copies[i]).(*FuncDecl) } } diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 23806e3ec70..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" @@ -491,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, @@ -504,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 { @@ -545,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 @@ -567,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 { 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/internal/txlog/txlog.go b/gnovm/pkg/gnolang/internal/txlog/txlog.go new file mode 100644 index 00000000000..cda11083672 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/txlog/txlog.go @@ -0,0 +1,141 @@ +// Package txlog is an internal package containing data structures that can +// function as "transaction logs" on top of a hash map (or other key/value +// data type implementing [Map]). +// +// A transaction log keeps track of the write operations performed in a +// transaction, so that they can be committed together, atomically, +// when calling [MapCommitter.Commit]. +package txlog + +// Map is a generic interface to a key/value map, like Go's builtin map. +type Map[K comparable, V any] interface { + Get(K) (V, bool) + Set(K, V) + Delete(K) + Iterate() func(yield func(K, V) bool) +} + +// MapCommitter is a Map which also implements a Commit() method, which writes +// to the underlying (parent) [Map]. +type MapCommitter[K comparable, V any] interface { + Map[K, V] + + // Commit writes the logged operations to the underlying map. + // After calling commit, the underlying tx log is cleared and the + // MapCommitter may be reused. + Commit() +} + +// GoMap is a simple implementation of [Map], which wraps the operations of +// Go's map builtin to implement [Map]. +type GoMap[K comparable, V any] map[K]V + +// Get implements [Map]. +func (m GoMap[K, V]) Get(k K) (V, bool) { + v, ok := m[k] + return v, ok +} + +// Set implements [Map]. +func (m GoMap[K, V]) Set(k K, v V) { + m[k] = v +} + +// Delete implements [Map]. +func (m GoMap[K, V]) Delete(k K) { + delete(m, k) +} + +// Iterate implements [Map]. +func (m GoMap[K, V]) Iterate() func(yield func(K, V) bool) { + return func(yield func(K, V) bool) { + for k, v := range m { + if !yield(k, v) { + return + } + } + } +} + +// Wrap wraps the map m into a data structure to keep a transaction log. +// To write data to m, use MapCommitter.Commit. +func Wrap[K comparable, V any](m Map[K, V]) MapCommitter[K, V] { + return &txLog[K, V]{ + source: m, + dirty: make(map[K]deletable[V]), + } +} + +type txLog[K comparable, V any] struct { + source Map[K, V] // read-only until Commit() + dirty map[K]deletable[V] // pending writes on source +} + +func (b *txLog[K, V]) Commit() { + // copy from b.dirty into b.source; clean b.dirty + for k, v := range b.dirty { + if v.deleted { + b.source.Delete(k) + } else { + b.source.Set(k, v.v) + } + } + b.dirty = make(map[K]deletable[V]) +} + +func (b txLog[K, V]) Get(k K) (V, bool) { + if bufValue, ok := b.dirty[k]; ok { + if bufValue.deleted { + var zeroV V + return zeroV, false + } + return bufValue.v, true + } + + return b.source.Get(k) +} + +func (b txLog[K, V]) Set(k K, v V) { + b.dirty[k] = deletable[V]{v: v} +} + +func (b txLog[K, V]) Delete(k K) { + b.dirty[k] = deletable[V]{deleted: true} +} + +func (b txLog[K, V]) Iterate() func(yield func(K, V) bool) { + return func(yield func(K, V) bool) { + // go through b.source; skip deleted values, and use updated values + // for those which exist in b.dirty. + b.source.Iterate()(func(k K, v V) bool { + if dirty, ok := b.dirty[k]; ok { + if dirty.deleted { + return true + } + return yield(k, dirty.v) + } + + // not in dirty + return yield(k, v) + }) + + // iterate over all "new" values (ie. exist in b.dirty but not b.source). + for k, v := range b.dirty { + if v.deleted { + continue + } + _, ok := b.source.Get(k) + if ok { + continue + } + if !yield(k, v.v) { + break + } + } + } +} + +type deletable[V any] struct { + v V + deleted bool +} diff --git a/gnovm/pkg/gnolang/internal/txlog/txlog_test.go b/gnovm/pkg/gnolang/internal/txlog/txlog_test.go new file mode 100644 index 00000000000..b0780fc8380 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/txlog/txlog_test.go @@ -0,0 +1,553 @@ +package txlog + +import ( + "fmt" + "maps" + "testing" + + "github.com/stretchr/testify/assert" +) + +func ExampleWrap() { + type User struct { + ID int + Name string + } + m := GoMap[int, User](map[int]User{ + 1: {ID: 1, Name: "alice"}, + 2: {ID: 2, Name: "bob"}, + }) + + // Wrap m in a transaction log. + txl := Wrap(m) + txl.Set(2, User{ID: 2, Name: "carl"}) + + // m will still have bob, while txl will have carl. + fmt.Println("m.Get(2):") + fmt.Println(m.Get(2)) + fmt.Println("txl.Get(2):") + fmt.Println(txl.Get(2)) + + // after txl.Commit(), the transaction log will be committed to m. + txl.Commit() + fmt.Println("--- commit ---") + fmt.Println("m.Get(2):") + fmt.Println(m.Get(2)) + fmt.Println("txl.Get(2):") + fmt.Println(txl.Get(2)) + + // Output: + // m.Get(2): + // {2 bob} true + // txl.Get(2): + // {2 carl} true + // --- commit --- + // m.Get(2): + // {2 carl} true + // txl.Get(2): + // {2 carl} true +} + +func Test_txLog(t *testing.T) { + t.Parallel() + + type Value = struct{} + + // Full "integration test" of the txLog + mapwrapper. + source := GoMap[int, *Value](map[int]*Value{}) + + // create 4 empty values (we'll just use the pointers) + vs := [...]*Value{ + {}, + {}, + {}, + {}, + } + source.Set(0, vs[0]) + source.Set(1, vs[1]) + source.Set(2, vs[2]) + + { + // Attempt getting, and deleting an item. + v, ok := source.Get(0) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[0] == v, "pointer returned should be ==") + + source.Delete(0) + v, ok = source.Get(0) + assert.False(t, ok, "should be unsuccessful Get") + assert.Nil(t, v, "pointer returned should be nil") + + verifyHashMapValues(t, source, map[int]*Value{1: vs[1], 2: vs[2]}) + } + + saved := maps.Clone(source) + txm := Wrap(source).(*txLog[int, *Value]) + + { + // Attempt getting, deleting an item on a buffered map; + // then creating a new one. + v, ok := txm.Get(1) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[1] == v, "pointer returned should be ==") + + txm.Delete(1) + v, ok = txm.Get(1) + assert.False(t, ok, "should be unsuccessful Get") + assert.Nil(t, v, "pointer returned should be nil") + + // Update an existing value to another value. + txm.Set(2, vs[0]) + v, ok = txm.Get(2) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[0] == v, "pointer returned should be ==") + + // Add a new value + txm.Set(3, vs[3]) + v, ok = txm.Get(3) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[3] == v, "pointer returned should be ==") + + // The original bufferedTxMap should still not know about the + // new value, and the internal "source" map should still be the + // same. + v, ok = source.Get(3) + assert.Nil(t, v) + assert.False(t, ok) + v, ok = source.Get(1) + assert.True(t, vs[1] == v) + assert.True(t, ok) + assert.Equal(t, saved, source) + assert.Equal(t, saved, txm.source) + + // double-check on the iterators. + verifyHashMapValues(t, source, map[int]*Value{1: vs[1], 2: vs[0]}) + verifyHashMapValues(t, txm, map[int]*Value{2: vs[2], 3: vs[3]}) + } + + { + // Using Commit() should cause txm's internal buffer to be cleared; + // and for all changes to show up on the source map. + txm.Commit() + assert.Empty(t, txm.dirty) + assert.Equal(t, source, txm.source) + assert.NotEqual(t, saved, source) + + v, ok := source.Get(3) + assert.True(t, vs[3] == v) + assert.True(t, ok) + v, ok = source.Get(1) + assert.Nil(t, v) + assert.False(t, ok) + + // double-check on the iterators. + verifyHashMapValues(t, source, map[int]*Value{2: vs[0], 3: vs[3]}) + verifyHashMapValues(t, txm, map[int]*Value{2: vs[0], 3: vs[3]}) + } +} + +func verifyHashMapValues(t *testing.T, m Map[int, *struct{}], expectedReadonly map[int]*struct{}) { + t.Helper() + + expected := maps.Clone(expectedReadonly) + m.Iterate()(func(k int, v *struct{}) bool { + ev, eok := expected[k] + _ = assert.True(t, eok, "mapping %d:%v should exist in expected map", k, v) && + assert.Equal(t, ev, v, "values should match") + delete(expected, k) + return true + }) + assert.Empty(t, expected, "(some) expected values not found in the Map") +} + +func Test_bufferedTxMap(t *testing.T) { + t.Parallel() + + type Value struct{} + + // Full "integration test" of the bufferedTxMap. + var m bufferedTxMap[int, *Value] + m.init() + + vs := [...]*Value{ + {}, + {}, + {}, + {}, + } + m.Set(0, vs[0]) + m.Set(1, vs[1]) + m.Set(2, vs[2]) + + { + // Attempt getting, and deleting an item. + v, ok := m.Get(0) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[0] == v, "pointer returned should be ==") + + m.Delete(0) + v, ok = m.Get(0) + assert.False(t, ok, "should be unsuccessful Get") + assert.Nil(t, v, "pointer returned should be nil") + } + + saved := maps.Clone(m.source) + bm := m.buffered() + + { + // Attempt getting, deleting an item on a buffered map; + // then creating a new one. + v, ok := bm.Get(1) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[1] == v, "pointer returned should be ==") + + bm.Delete(1) + v, ok = bm.Get(1) + assert.False(t, ok, "should be unsuccessful Get") + assert.Nil(t, v, "pointer returned should be nil") + + bm.Set(3, vs[3]) + v, ok = bm.Get(3) + assert.True(t, ok, "should be successful Get") + assert.True(t, vs[3] == v, "pointer returned should be ==") + + // The original bufferedTxMap should still not know about the + // new value, and the internal "source" map should still be the + // same. + v, ok = m.Get(3) + assert.Nil(t, v) + assert.False(t, ok) + v, ok = m.Get(1) + assert.True(t, vs[1] == v) + assert.True(t, ok) + assert.Equal(t, saved, m.source) + assert.Equal(t, saved, bm.source) + } + + { + // Using write() should cause bm's internal buffer to be cleared; + // and for all changes to show up on the source map. + bm.write() + assert.Empty(t, bm.dirty) + assert.Equal(t, m.source, bm.source) + assert.NotEqual(t, saved, m.source) + + v, ok := m.Get(3) + assert.True(t, vs[3] == v) + assert.True(t, ok) + v, ok = m.Get(1) + assert.Nil(t, v) + assert.False(t, ok) + } +} + +func Test_bufferedTxMap_initErr(t *testing.T) { + t.Parallel() + + var b bufferedTxMap[bool, bool] + b.init() + + assert.PanicsWithValue(t, "cannot init with a dirty buffer", func() { + buf := b.buffered() + buf.init() + }) +} + +func Test_bufferedTxMap_bufferedErr(t *testing.T) { + t.Parallel() + + var b bufferedTxMap[bool, bool] + b.init() + buf := b.buffered() + + assert.PanicsWithValue(t, "cannot stack multiple bufferedTxMap", func() { + buf.buffered() + }) +} + +// bufferedTxMap is a wrapper around the map type, supporting regular Get, Set +// and Delete operations. Additionally, it can create a "buffered" version of +// itself, which will keep track of all write (set and delete) operations to the +// map; so that they can all be atomically committed when calling "write". +type bufferedTxMap[K comparable, V any] struct { + source map[K]V + dirty map[K]deletable[V] +} + +// init should be called when creating the bufferedTxMap, in a non-buffered +// context. +func (b *bufferedTxMap[K, V]) init() { + if b.dirty != nil { + panic("cannot init with a dirty buffer") + } + b.source = make(map[K]V) +} + +// buffered creates a copy of b, which has a usable dirty map. +func (b bufferedTxMap[K, V]) buffered() bufferedTxMap[K, V] { + if b.dirty != nil { + panic("cannot stack multiple bufferedTxMap") + } + return bufferedTxMap[K, V]{ + source: b.source, + dirty: make(map[K]deletable[V]), + } +} + +// write commits the data in dirty to the map in source. +func (b *bufferedTxMap[K, V]) write() { + for k, v := range b.dirty { + if v.deleted { + delete(b.source, k) + } else { + b.source[k] = v.v + } + } + b.dirty = make(map[K]deletable[V]) +} + +func (b bufferedTxMap[K, V]) Get(k K) (V, bool) { + if b.dirty != nil { + if bufValue, ok := b.dirty[k]; ok { + if bufValue.deleted { + var zeroV V + return zeroV, false + } + return bufValue.v, true + } + } + v, ok := b.source[k] + return v, ok +} + +func (b bufferedTxMap[K, V]) Set(k K, v V) { + if b.dirty == nil { + b.source[k] = v + return + } + b.dirty[k] = deletable[V]{v: v} +} + +func (b bufferedTxMap[K, V]) Delete(k K) { + if b.dirty == nil { + delete(b.source, k) + return + } + b.dirty[k] = deletable[V]{deleted: true} +} + +func Benchmark_txLogRead(b *testing.B) { + const maxValues = (1 << 10) * 9 // must be multiple of 9 + + var ( + baseMap = make(map[int]int) // all values filled + wrapped = GoMap[int, int](baseMap) // wrapper around baseMap + stack1 = Wrap(wrapped) // n+1, n+4, n+7 values filled (n%9 == 0) + stack2 = Wrap(stack1) // n'th values filled (n%9 == 0) + ) + + for i := 0; i < maxValues; i++ { + baseMap[i] = i + switch i % 9 { + case 1, 4, 7: + stack1.Set(i, i+1_000_000) + case 0: + stack2.Set(i, i+10_000_000) + } + } + + var v int + var ok bool + _, _ = v, ok + + // through closure, so func calls have to go through "indirection". + runbench := func(b *testing.B, src Map[int, int]) { //nolint:thelper + for i := 0; i < b.N; i++ { + v, ok = src.Get(i % maxValues) + } + } + + b.Run("stack2", func(b *testing.B) { runbench(b, stack2) }) + b.Run("stack1", func(b *testing.B) { runbench(b, stack1) }) + b.Run("wrapped", func(b *testing.B) { runbench(b, wrapped) }) + b.Run("baseline", func(b *testing.B) { + for i := 0; i < b.N; i++ { + v, ok = baseMap[i%maxValues] + } + }) +} + +func Benchmark_txLogWrite(b *testing.B) { + // after this amount of values, the maps are re-initialized. + // you can tweak this to see how the benchmarks behave on a variety of + // values. + // NOTE: setting this too high will skew the benchmark in favour those which + // have a smaller N, as those with a higher N have to allocate more in a + // single map. + const maxValues = 1 << 15 // 32768 + + var v int + var ok bool + _, _ = v, ok + + b.Run("stack1", func(b *testing.B) { + src := GoMap[int, int](make(map[int]int)) + st := Wrap(src) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + k := i % maxValues + + st.Set(k, i) + // we use this assignment to prevent the compiler from optimizing + // out code, especially in the baseline case. + v, ok = st.Get(k) + + if k == maxValues-1 { + st = Wrap(src) + } + } + }) + b.Run("wrapped", func(b *testing.B) { + src := GoMap[int, int](make(map[int]int)) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + k := i % maxValues + + src.Set(k, i) + // we use this assignment to prevent the compiler from optimizing + // out code, especially in the baseline case. + v, ok = src.Get(k) + + if k == maxValues-1 { + src = GoMap[int, int](make(map[int]int)) + } + } + }) + b.Run("baseline", func(b *testing.B) { + // this serves to have a baseline value in the benchmark results + // for when we just use a map directly. + m := make(map[int]int) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + k := i % maxValues + + m[k] = i + v, ok = m[k] + + if k == maxValues-1 { + m = make(map[int]int) + } + } + }) +} + +func Benchmark_bufferedTxMapRead(b *testing.B) { + const maxValues = (1 << 10) * 9 // must be multiple of 9 + + var ( + baseMap = make(map[int]int) // all values filled + wrapped = bufferedTxMap[int, int]{source: baseMap} + stack1 = wrapped.buffered() // n, n+1, n+4, n+7 values filled (n%9 == 0) + // this test doesn't have stack2 as bufferedTxMap + // does not support stacking + ) + + for i := 0; i < maxValues; i++ { + baseMap[i] = i + switch i % 9 { + case 0, 1, 4, 7: + stack1.Set(i, i+1_000_000) + } + } + + var v int + var ok bool + _, _ = v, ok + + b.Run("stack1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // use assignment to avoid the compiler optimizing out the loops + v, ok = stack1.Get(i % maxValues) + } + }) + b.Run("wrapped", func(b *testing.B) { + for i := 0; i < b.N; i++ { + v, ok = wrapped.Get(i % maxValues) + } + }) + b.Run("baseline", func(b *testing.B) { + for i := 0; i < b.N; i++ { + v, ok = baseMap[i%maxValues] + } + }) +} + +func Benchmark_bufferedTxMapWrite(b *testing.B) { + // after this amount of values, the maps are re-initialized. + // you can tweak this to see how the benchmarks behave on a variety of + // values. + // NOTE: setting this too high will skew the benchmark in favour those which + // have a smaller N, as those with a higher N have to allocate more in a + // single map. + const maxValues = 1 << 15 // 32768 + + var v int + var ok bool + _, _ = v, ok + + b.Run("buffered", func(b *testing.B) { + var orig bufferedTxMap[int, int] + orig.init() + txm := orig.buffered() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + k := i % maxValues + + txm.Set(k, i) + // we use this assignment to prevent the compiler from optimizing + // out code, especially in the baseline case. + v, ok = txm.Get(k) + + if k == maxValues-1 { + txm = orig.buffered() + } + } + }) + b.Run("unbuffered", func(b *testing.B) { + var txm bufferedTxMap[int, int] + txm.init() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + k := i % maxValues + + txm.Set(k, i) + v, ok = txm.Get(k) + + if k == maxValues-1 { + txm.init() + } + } + }) + b.Run("baseline", func(b *testing.B) { + // this serves to have a baseline value in the benchmark results + // for when we just use a map directly. + m := make(map[int]int) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + k := i % maxValues + + m[k] = i + v, ok = m[k] + + if k == maxValues-1 { + m = make(map[int]int) + } + } + }) +} diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index c8afd774ab9..d9aaa6181b7 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -25,12 +25,23 @@ type Exception struct { // Frame is used to reference the frame a panic occurred in so that recover() knows if the // currently executing deferred function is able to recover from the panic. Frame *Frame + + Stacktrace Stacktrace } func (e Exception) Sprint(m *Machine) string { return e.Value.Sprint(m) } +// UnhandledPanicError represents an error thrown when a panic is not handled in the realm. +type UnhandledPanicError struct { + Descriptor string // Description of the unhandled panic. +} + +func (e UnhandledPanicError) Error() string { + return e.Descriptor +} + //---------------------------------------- // Machine @@ -466,6 +477,43 @@ func (m *Machine) TestFunc(t *testing.T, tv TypedValue) { }) } +// Stacktrace returns the stack trace of the machine. +// It collects the executions and frames from the machine's frames and statements. +func (m *Machine) Stacktrace() (stacktrace Stacktrace) { + if len(m.Frames) == 0 { + return + } + + calls := make([]StacktraceCall, 0, len(m.Stmts)) + nextStmtIndex := len(m.Stmts) - 1 + for i := len(m.Frames) - 1; i >= 0; i-- { + if m.Frames[i].IsCall() { + stm := m.Stmts[nextStmtIndex] + bs := stm.(*bodyStmt) + stm = bs.Body[bs.NextBodyIndex-1] + calls = append(calls, StacktraceCall{ + Stmt: stm, + Frame: m.Frames[i], + }) + } + // if the frame is a call, the next statement is the last statement of the frame. + nextStmtIndex = m.Frames[i].NumStmts - 1 + } + + // if the stacktrace is too long, we trim it down to maxStacktraceSize + if len(calls) > maxStacktraceSize { + const halfMax = maxStacktraceSize / 2 + + stacktrace.NumFramesElided = len(calls) - maxStacktraceSize + calls = append(calls[:halfMax], calls[len(calls)-halfMax:]...) + calls = calls[:len(calls):len(calls)] // makes remaining part of "calls" GC'able + } + + stacktrace.Calls = calls + + return +} + // in case of panic, inject location information to exception. func (m *Machine) injectLocOnPanic() { if r := recover(); r != nil { @@ -745,8 +793,14 @@ func (m *Machine) resavePackageValues(rlm *Realm) { func (m *Machine) RunFunc(fn Name) { defer func() { if r := recover(); r != nil { - fmt.Printf("Machine.RunFunc(%q) panic: %v\n%s\n", - fn, r, m.String()) + switch r := r.(type) { + case UnhandledPanicError: + fmt.Printf("Machine.RunFunc(%q) panic: %s\nStacktrace: %s\n", + fn, r.Error(), m.ExceptionsStacktrace()) + default: + fmt.Printf("Machine.RunFunc(%q) panic: %v\nMachine State:%s\nStacktrace: %s\n", + fn, r, m.String(), m.Stacktrace().String()) + } panic(r) } }() @@ -756,8 +810,14 @@ func (m *Machine) RunFunc(fn Name) { func (m *Machine) RunMain() { defer func() { if r := recover(); r != nil { - fmt.Printf("Machine.RunMain() panic: %v\n%s\n", - r, m.String()) + switch r := r.(type) { + case UnhandledPanicError: + fmt.Printf("Machine.RunMain() panic: %s\nStacktrace: %s\n", + r.Error(), m.ExceptionsStacktrace()) + default: + fmt.Printf("Machine.RunMain() panic: %v\nMachine State:%s\nStacktrace: %s\n", + r, m.String(), m.Stacktrace()) + } panic(r) } }() @@ -1615,11 +1675,11 @@ func (m *Machine) PopStmt() Stmt { } if bs, ok := s.(*bodyStmt); ok { return bs.PopActiveStmt() - } else { - // general case. - m.Stmts = m.Stmts[:numStmts-1] - return s } + + m.Stmts = m.Stmts[:numStmts-1] + + return s } func (m *Machine) ForcePopStmt() (s Stmt) { @@ -1871,6 +1931,7 @@ func (m *Machine) PopFrame() Frame { m.Printf("-F %#v\n", f) } m.Frames = m.Frames[:numFrames-1] + return *f } @@ -1890,8 +1951,7 @@ func (m *Machine) PopFrameAndReturn() { fr := m.PopFrame() fr.Popped = true if debug { - // TODO: optimize with fr.IsCall - if fr.Func == nil && fr.GoFunc == nil { + if !fr.IsCall() { panic("unexpected non-call (loop) frame") } } @@ -1967,8 +2027,7 @@ func (m *Machine) lastCallFrame(n int, mustBeFound bool) *Frame { } for i := len(m.Frames) - 1; i >= 0; i-- { fr := m.Frames[i] - if fr.Func != nil || fr.GoFunc != nil { - // TODO: optimize with fr.IsCall + if fr.IsCall() { if n == 1 { return fr } else { @@ -1989,8 +2048,7 @@ func (m *Machine) lastCallFrame(n int, mustBeFound bool) *Frame { func (m *Machine) PopUntilLastCallFrame() *Frame { for i := len(m.Frames) - 1; i >= 0; i-- { fr := m.Frames[i] - if fr.Func != nil || fr.GoFunc != nil { - // TODO: optimize with fr.IsCall + if fr.IsCall() { m.Frames = m.Frames[:i+1] return fr } @@ -2101,8 +2159,9 @@ func (m *Machine) Panic(ex TypedValue) { m.Exceptions = append( m.Exceptions, Exception{ - Value: ex, - Frame: m.MustLastCallFrame(1), + Value: ex, + Frame: m.MustLastCallFrame(1), + Stacktrace: m.Stacktrace(), }, ) @@ -2171,7 +2230,8 @@ func (m *Machine) String() string { builder.WriteString(" Blocks:\n") - for b := m.LastBlock(); b != nil; { + for i := len(m.Blocks) - 1; i > 0; i-- { + b := m.Blocks[i] gen := builder.Len()/3 + 1 gens := "@" // strings.Repeat("@", gen) @@ -2247,6 +2307,30 @@ func (m *Machine) String() string { return builder.String() } +func (m *Machine) ExceptionsStacktrace() string { + if len(m.Exceptions) == 0 { + return "" + } + + var builder strings.Builder + + ex := m.Exceptions[0] + builder.WriteString("panic: " + ex.Sprint(m) + "\n") + builder.WriteString(ex.Stacktrace.String()) + + switch { + case len(m.Exceptions) > 2: + fmt.Fprintf(&builder, "... %d panic(s) elided ...\n", len(m.Exceptions)-2) + fallthrough // to print last exception + case len(m.Exceptions) == 2: + ex = m.Exceptions[len(m.Exceptions)-1] + builder.WriteString("panic: " + ex.Sprint(m) + "\n") + builder.WriteString(ex.Stacktrace.String()) + } + + return builder.String() +} + //---------------------------------------- // utility diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index a05de8c74aa..7f7ce0b3a87 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -150,6 +150,10 @@ func isReservedName(n Name) bool { // scans uverse static node for blocknames. (slow) func isUverseName(n Name) bool { + if n == "panic" { + // panic is not in uverse, as it is parsed as its own statement (PanicStmt) + return true + } uverseNames := UverseNode().GetBlockNames() for _, name := range uverseNames { if name == n { diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index eb51f890072..b18ed157ca6 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -4,6 +4,7 @@ import ( "fmt" "go/parser" "go/token" + "math" "os" "path/filepath" "reflect" @@ -672,6 +673,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() { @@ -1098,7 +1103,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. @@ -1111,7 +1116,6 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { panic(err) } allowedFiles := []string{ // make case insensitive? - "gno.mod", "LICENSE", "README.md", } @@ -1376,6 +1380,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. @@ -1481,6 +1492,7 @@ type BlockNode interface { Define(Name, TypedValue) Define2(bool, Name, Type, TypedValue) GetBody() Body + SetBody(Body) } // ---------------------------------------- @@ -1791,7 +1803,7 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { if int(sb.NumNames) != len(sb.Names) { panic("StaticBlock.NumNames and len(.Names) mismatch") } - if (1<<16 - 1) < sb.NumNames { + if sb.NumNames == math.MaxUint16 { panic("too many variables in block") } if tv.T == nil && tv.V != nil { @@ -1874,18 +1886,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/nodes_test.go b/gnovm/pkg/gnolang/nodes_test.go new file mode 100644 index 00000000000..2c3a03d8c09 --- /dev/null +++ b/gnovm/pkg/gnolang/nodes_test.go @@ -0,0 +1,44 @@ +package gnolang_test + +import ( + "math" + "testing" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func TestStaticBlock_Define2_MaxNames(t *testing.T) { + defer func() { + if r := recover(); r != nil { + panicString, ok := r.(string) + if !ok { + t.Errorf("expected panic string, got %v", r) + } + + if panicString != "too many variables in block" { + t.Errorf("expected panic string to be 'too many variables in block', got '%s'", panicString) + } + + return + } + + // If it didn't panic, fail. + t.Errorf("expected panic when exceeding maximum number of names") + }() + + staticBlock := new(gnolang.StaticBlock) + staticBlock.NumNames = math.MaxUint16 - 1 + staticBlock.Names = make([]gnolang.Name, staticBlock.NumNames) + + // Adding one more is okay. + staticBlock.Define2(false, gnolang.Name("a"), gnolang.BoolType, gnolang.TypedValue{T: gnolang.BoolType}) + if staticBlock.NumNames != math.MaxUint16 { + t.Errorf("expected NumNames to be %d, got %d", math.MaxUint16, staticBlock.NumNames) + } + if len(staticBlock.Names) != math.MaxUint16 { + t.Errorf("expected len(Names) to be %d, got %d", math.MaxUint16, len(staticBlock.Names)) + } + + // This one should panic because the maximum number of names has been reached. + staticBlock.Define2(false, gnolang.Name("a"), gnolang.BoolType, gnolang.TypedValue{T: gnolang.BoolType}) +} diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index a1861ed3aaa..db3c1e5695c 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -79,7 +79,6 @@ func (m *Machine) doOpEql() { if debug { debugAssertEqualityTypes(lv.T, rv.T) } - // set result in lv. res := isEql(m.Store, lv, rv) lv.T = UntypedBoolType @@ -344,6 +343,9 @@ func isEql(store Store, lv, rv *TypedValue) bool { } else if rvu { return false } + if err := checkSame(lv.T, rv.T, ""); err != nil { + return false + } if lnt, ok := lv.T.(*NativeType); ok { if rnt, ok := rv.T.(*NativeType); ok { if lnt.Type != rnt.Type { @@ -475,6 +477,12 @@ func isEql(store Store, lv, rv *TypedValue) bool { rfv.GetClosure(store) } case PointerKind: + if lv.T != rv.T && + lv.T.Elem() != DataByteType && + lv.T.TypeID() != rv.T.TypeID() { + return false + } + if lv.V != nil && rv.V != nil { lpv := lv.V.(PointerValue) rpv := rv.V.(PointerValue) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 5479ee6d5ae..15531ec610d 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -427,7 +427,9 @@ func (m *Machine) doOpPanic2() { for i, ex := range m.Exceptions { exs[i] = ex.Sprint(m) } - panic(strings.Join(exs, "\n\t")) + panic(UnhandledPanicError{ + Descriptor: strings.Join(exs, "\n\t"), + }) } m.PushOp(OpPanic2) m.PushOp(OpReturnCallDefers) // XXX rename, not return? diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 4bbeef2dace..8ff0b5bd538 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -194,8 +194,13 @@ func (m *Machine) doOpRef() { nv.Value = rv2 } } + // when obtaining a pointer of the databyte type, use the ElemType of databyte + elt := xv.TV.T + if elt == DataByteType { + elt = xv.TV.V.(DataByteValue).ElemType + } m.PushValue(TypedValue{ - T: m.Alloc.NewType(&PointerType{Elt: xv.TV.T}), + T: m.Alloc.NewType(&PointerType{Elt: elt}), V: xv, }) } @@ -238,14 +243,14 @@ func (m *Machine) doOpTypeAssert1() { // t is Gno interface. // assert that x implements type. - var impl bool - 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 } diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index a3f756ef58a..9168fc6f7c1 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "reflect" + "strings" "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" @@ -44,11 +45,12 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // skip declarations already predefined // (e.g. through recursion for a // dependent) - } else { - // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) - fn.Decls[i] = d2 + continue } + + // recursively predefine dependencies. + d2, _ := predefineNow(store, fn, d) + fn.Decls[i] = d2 } } } @@ -62,11 +64,12 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // skip declarations already predefined // (e.g. through recursion for a // dependent) - } else { - // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) - fn.Decls[i] = d2 + continue } + + // recursively predefine dependencies. + d2, _ := predefineNow(store, fn, d) + fn.Decls[i] = d2 } } } @@ -80,11 +83,12 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // skip declarations already predefined // (e.g. through recursion for a // dependent) - } else { - // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) - fn.Decls[i] = d2 + continue } + + // recursively predefine dependencies. + d2, _ := predefineNow(store, fn, d) + fn.Decls[i] = d2 } } } @@ -96,11 +100,36 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { if d.GetAttribute(ATTR_PREDEFINED) == true { // skip declarations already predefined (e.g. // through recursion for a dependent) - } else { - // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) - fn.Decls[i] = d2 + continue + } + + if vd, ok := d.(*ValueDecl); ok && len(vd.NameExprs) > 1 && len(vd.Values) == len(vd.NameExprs) { + split := make([]Decl, len(vd.NameExprs)) + + for j := 0; j < len(vd.NameExprs); j++ { + base := vd.Copy().(*ValueDecl) + base.NameExprs = NameExprs{NameExpr{ + Attributes: base.NameExprs[j].Attributes, + Path: base.NameExprs[j].Path, + Name: base.NameExprs[j].Name, + }} + + if j < len(base.Values) { + base.Values = Exprs{base.Values[j].Copy().(Expr)} + } + + split[j], _ = predefineNow(store, fn, base) + } + + fn.Decls = append(fn.Decls[:i], append(split, fn.Decls[i+1:]...)...) //nolint:makezero + i += len(vd.NameExprs) + continue } + + // recursively predefine dependencies. + d2, _ := predefineNow(store, fn, d) + + fn.Decls[i] = d2 } } } @@ -426,7 +455,26 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { switch n := n.(type) { // TRANS_ENTER ----------------------- case *AssignStmt: + checkValDefineMismatch(n) + if n.Op == DEFINE { + for _, lx := range n.Lhs { + ln := lx.(*NameExpr).Name + if ln == blankIdentifier { + // ignore. + } else if strings.HasPrefix(string(ln), ".decompose_") { + _, ok := last.GetLocalIndex(ln) + if !ok { + // initial declaration to be re-defined. + last.Predefine(false, ln) + } else { + // do not redeclare. + } + } + } + } else { + // nothing defined. + } // TRANS_ENTER ----------------------- case *ImportDecl, *ValueDecl, *TypeDecl, *FuncDecl: // NOTE func decl usually must happen with a @@ -438,8 +486,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // skip declarations already predefined // (e.g. through recursion for a dependent) } else { + d := n.(Decl) + if cd, ok := d.(*ValueDecl); ok { + checkValDefineMismatch(cd) + } // recursively predefine dependencies. - d2, ppd := predefineNow(store, last, n.(Decl)) + d2, ppd := predefineNow(store, last, d) if ppd { return d2, TRANS_SKIP } else { @@ -1183,6 +1235,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { "incompatible types in binary expression: %v %v %v", lt.TypeID(), n.Op, rt.TypeID())) } + // convert untyped to typed + checkOrConvertType(store, last, &n.Left, defaultTypeOf(lt), false) + checkOrConvertType(store, last, &n.Right, defaultTypeOf(rt), false) } else { // left untyped, right typed checkOrConvertType(store, last, &n.Left, rt, false) } @@ -1479,6 +1534,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { checkOrConvertIntegerKind(store, last, n.High) checkOrConvertIntegerKind(store, last, n.Max) + // if n.X is untyped, convert to corresponding type + t := evalStaticTypeOf(store, last, n.X) + if isUntyped(t) { + dt := defaultTypeOf(t) + checkOrConvertType(store, last, &n.X, dt, false) + } + // TRANS_LEAVE ----------------------- case *TypeAssertExpr: if n.Type == nil { @@ -1872,7 +1934,88 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.) // NOTE: Keep in sync with DEFINE above. if len(n.Lhs) > len(n.Rhs) { - // check is done in assertCompatible + // 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) + // 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 + } + } + 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 { @@ -2058,13 +2201,42 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { numNames := len(n.NameExprs) sts := make([]Type, numNames) // static types tvs := make([]TypedValue, numNames) + if numNames > 1 && len(n.Values) == 1 { - // special case if `var a, b, c T? = f()` form. - cx := n.Values[0].(*CallExpr) - tt := evalStaticTypeOfRaw(store, last, cx).(*tupleType) - if len(tt.Elts) != numNames { - panic("should not happen") + // Special cases if one of the following: + // - `var a, b, c T = f()` + // - `var a, b = n.(T)` + // - `var a, b = n[i], where n is a map` + + var tuple *tupleType + valueExpr := n.Values[0] + valueType := evalStaticTypeOfRaw(store, last, valueExpr) + + switch expr := valueExpr.(type) { + case *CallExpr: + tuple = valueType.(*tupleType) + case *TypeAssertExpr, *IndexExpr: + tuple = &tupleType{Elts: []Type{valueType, BoolType}} + if ex, ok := expr.(*TypeAssertExpr); ok { + ex.HasOK = true + break + } + expr.(*IndexExpr).HasOK = true + default: + panic(fmt.Sprintf("unexpected ValueDecl value expression type %T", expr)) } + + if rLen := len(tuple.Elts); rLen != numNames { + panic( + fmt.Sprintf( + "assignment mismatch: %d variable(s) but %s returns %d value(s)", + numNames, + valueExpr.String(), + rLen, + ), + ) + } + if n.Type != nil { // only a single type can be specified. nt := evalStaticType(store, last, n.Type) @@ -2076,14 +2248,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // set types as return types. for i := 0; i < numNames; i++ { - et := tt.Elts[i] + et := tuple.Elts[i] sts[i] = et tvs[i] = anyValue(et) } } } else if len(n.Values) != 0 && numNames != len(n.Values) { - panic("should not happen") + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, len(n.Values))) } else { // general case + for _, v := range n.Values { + if cx, ok := v.(*CallExpr); ok { + tt, ok := evalStaticTypeOfRaw(store, last, cx).(*tupleType) + if ok && len(tt.Elts) != 1 { + panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", cx.Func.String(), tt.Elts)) + } + } + } // evaluate types and convert consts. if n.Type != nil { // only a single type can be specified. @@ -2305,7 +2485,7 @@ func evalStaticType(store Store, last BlockNode, x Expr) Type { // See comment in evalStaticTypeOfRaw. if store != nil && pn.PkgPath != uversePkgPath { pv := pn.NewPackage() // temporary - store = store.Fork() + store = store.BeginTransaction(nil, nil) store.SetCachePackage(pv) } m := NewMachine(pn.PkgPath, store) @@ -2379,7 +2559,7 @@ func evalStaticTypeOfRaw(store Store, last BlockNode, x Expr) (t Type) { // yet predefined this time around. if store != nil && pn.PkgPath != uversePkgPath { pv := pn.NewPackage() // temporary - store = store.Fork() + store = store.BeginTransaction(nil, nil) store.SetCachePackage(pv) } m := NewMachine(pn.PkgPath, store) @@ -2681,8 +2861,10 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative // push t into bx.Left checkOrConvertType(store, last, &bx.Left, t, autoNative) return - // case EQL, LSS, GTR, NEQ, LEQ, GEQ: - // default: + case EQL, LSS, GTR, NEQ, LEQ, GEQ: + // do nothing + default: + // do nothing } } } @@ -2793,6 +2975,92 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } +func assertTypeDeclNoCycle(store Store, last BlockNode, td *TypeDecl, stack *[]Name) { + assertTypeDeclNoCycle2(store, last, td.Type, stack, false, td.IsAlias) +} + +func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool, isAlias bool) { + if x == nil { + panic("unexpected nil expression when checking for type declaration cycles") + } + + var lastX Expr + defer func() { + if _, ok := lastX.(*NameExpr); ok { + // pop stack + *stack = (*stack)[:len(*stack)-1] + } + }() + + switch cx := x.(type) { + case *NameExpr: + var msg string + + // Function to build the error message + buildMessage := func() string { + for j := 0; j < len(*stack); j++ { + msg += fmt.Sprintf("%s -> ", (*stack)[j]) + } + return msg + string(cx.Name) // Append the current name last + } + + // Check for existence of cx.Name in stack + findCycle := func() { + for _, n := range *stack { + if n == cx.Name { + msg = buildMessage() + panic(fmt.Sprintf("invalid recursive type: %s", msg)) + } + } + } + + if indirect && !isAlias { + *stack = (*stack)[:0] + } else { + findCycle() + *stack = append(*stack, cx.Name) + lastX = cx + } + + return + case *SelectorExpr: + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect, isAlias) + case *StarExpr: + assertTypeDeclNoCycle2(store, last, cx.X, stack, true, isAlias) + case *FieldTypeExpr: + assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect, isAlias) + case *ArrayTypeExpr: + if cx.Len != nil { + assertTypeDeclNoCycle2(store, last, cx.Len, stack, indirect, isAlias) + } + assertTypeDeclNoCycle2(store, last, cx.Elt, stack, indirect, isAlias) + case *SliceTypeExpr: + assertTypeDeclNoCycle2(store, last, cx.Elt, stack, true, isAlias) + case *InterfaceTypeExpr: + for i := range cx.Methods { + assertTypeDeclNoCycle2(store, last, &cx.Methods[i], stack, indirect, isAlias) + } + case *ChanTypeExpr: + assertTypeDeclNoCycle2(store, last, cx.Value, stack, true, isAlias) + case *FuncTypeExpr: + for i := range cx.Params { + assertTypeDeclNoCycle2(store, last, &cx.Params[i], stack, true, isAlias) + } + for i := range cx.Results { + assertTypeDeclNoCycle2(store, last, &cx.Results[i], stack, true, isAlias) + } + case *MapTypeExpr: + assertTypeDeclNoCycle2(store, last, cx.Key, stack, true, isAlias) + assertTypeDeclNoCycle2(store, last, cx.Value, stack, true, isAlias) + case *StructTypeExpr: + for i := range cx.Fields { + assertTypeDeclNoCycle2(store, last, &cx.Fields[i], stack, indirect, isAlias) + } + default: + } + return +} + // 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 @@ -3084,11 +3352,11 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { } } }() - m := make(map[Name]struct{}) - return predefineNow2(store, last, d, m) + stack := &[]Name{} + return predefineNow2(store, last, d, stack) } -func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (Decl, bool) { +func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bool) { pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. for _, dn := range d.GetDeclNames() { @@ -3096,15 +3364,24 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De panic(fmt.Sprintf( "builtin identifiers cannot be shadowed: %s", dn)) } - m[dn] = struct{}{} + *stack = append(*stack, dn) + } + + // check type decl cycle + if td, ok := d.(*TypeDecl); ok { + // recursively check + assertTypeDeclNoCycle(store, last, td, stack) } + // recursively predefine dependencies. for { un := tryPredefine(store, last, d) if un != "" { // check circularity. - if _, ok := m[un]; ok { - panic(fmt.Sprintf("constant definition loop with %s", un)) + for _, n := range *stack { + if n == un { + panic(fmt.Sprintf("constant definition loop with %s", un)) + } } // look up dependency declaration from fileset. file, decl := pkg.FileSet.GetDeclFor(un) @@ -3113,7 +3390,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De panic("all types from files in file-set should have already been predefined") } // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, m) + *decl, _ = predefineNow2(store, file, *decl, stack) } else { break } @@ -3160,7 +3437,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, @@ -3343,7 +3620,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", @@ -3388,6 +3665,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { pkg := skipFile(last).(*PackageNode) // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. + // The body may get altered during preprocessing later. fv := &FuncValue{ Type: ft, IsMethod: false, diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 038f4ba894b..8a1743ddf53 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -2,17 +2,16 @@ package gnolang import ( "fmt" - "maps" "reflect" "slices" "strconv" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/txlog" "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" ) @@ -32,8 +31,12 @@ type PackageInjector func(store Store, pn *PackageNode) // NativeStore is a function which can retrieve native bodies of native functions. type NativeStore func(pkgName string, name Name) func(m *Machine) +// Store is the central interface that specifies the communications between the +// GnoVM and the underlying data store; currently, generally the Gno.land +// blockchain, or the file system. type Store interface { // STABLE + BeginTransaction(baseStore, iavlStore store.Store) TransactionStore SetPackageGetter(PackageGetter) GetPackage(pkgPath string, isImport bool) *PackageValue SetCachePackage(*PackageValue) @@ -62,9 +65,7 @@ type Store interface { GetMemPackage(path string) *std.MemPackage GetMemFile(path string, name string) *std.MemFile IterMemPackage() <-chan *std.MemPackage - ClearObjectCache() // for each delivertx. - Fork() Store // for checktx, simulate, and queries. - SwapStores(baseStore, iavlStore store.Store) // for gas wrappers. + ClearObjectCache() // run before processing a message SetPackageInjector(PackageInjector) // for natives SetNativeStore(NativeStore) // for "new" natives XXX GetNative(pkgPath string, name Name) func(m *Machine) // for "new" natives XXX @@ -73,20 +74,33 @@ type Store interface { LogSwitchRealm(rlmpath string) // to mark change of realm boundaries ClearCache() Print() +} + +// TransactionStore is a store where the operations modifying the underlying store's +// caches are temporarily held in a buffer, and then executed together after +// executing Write. +type TransactionStore interface { + Store + + // Write commits the current buffered transaction data to the underlying store. + // It also clears the current buffer of the transaction. Write() - Flush() } -// Used to keep track of in-mem objects during tx. type defaultStore struct { - alloc *Allocator // for accounting for cached items - pkgGetter PackageGetter // non-realm packages - cacheObjects map[ObjectID]Object - cacheTypes map[TypeID]Type - cacheNodes map[Location]BlockNode - cacheNativeTypes map[reflect.Type]Type // go spec: reflect.Type are comparable - baseStore store.Store // for objects, types, nodes - iavlStore store.Store // for escaped object hashes + // underlying stores used to keep data + baseStore store.Store // for objects, types, nodes + iavlStore store.Store // for escaped object hashes + + // transaction-scoped + cacheObjects map[ObjectID]Object // this is a real cache, reset with every transaction. + cacheTypes txlog.Map[TypeID, Type] // this re-uses the parent store's. + cacheNodes txlog.Map[Location, BlockNode] // until BlockNode persistence is implemented, this is an actual store. + alloc *Allocator // for accounting for cached items + + // store configuration; cannot be modified in a transaction + pkgGetter PackageGetter // non-realm packages + cacheNativeTypes map[reflect.Type]Type // reflect doc: reflect.Type are comparable pkgInjector PackageInjector // for injecting natives nativeStore NativeStore // for injecting natives go2gnoStrict bool // if true, native->gno type conversion must be registered. @@ -98,28 +112,119 @@ type defaultStore struct { func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { ds := &defaultStore{ - alloc: alloc, + baseStore: baseStore, + iavlStore: iavlStore, + alloc: alloc, + + // cacheObjects is set; objects in the store will be copied over for any transaction. + cacheObjects: make(map[ObjectID]Object), + cacheTypes: txlog.GoMap[TypeID, Type](map[TypeID]Type{}), + cacheNodes: txlog.GoMap[Location, BlockNode](map[Location]BlockNode{}), + + // store configuration pkgGetter: nil, - cacheObjects: make(map[ObjectID]Object), - cacheTypes: make(map[TypeID]Type), - cacheNodes: make(map[Location]BlockNode), cacheNativeTypes: make(map[reflect.Type]Type), - baseStore: baseStore, - iavlStore: iavlStore, + pkgInjector: nil, + nativeStore: nil, go2gnoStrict: true, } InitStoreCaches(ds) return ds } +// If nil baseStore and iavlStore, the baseStores are re-used. +func (ds *defaultStore) BeginTransaction(baseStore, iavlStore store.Store) TransactionStore { + if baseStore == nil { + baseStore = ds.baseStore + } + if iavlStore == nil { + iavlStore = ds.iavlStore + } + ds2 := &defaultStore{ + // underlying stores + baseStore: baseStore, + iavlStore: iavlStore, + + // transaction-scoped + cacheObjects: make(map[ObjectID]Object), + cacheTypes: txlog.Wrap(ds.cacheTypes), + cacheNodes: txlog.Wrap(ds.cacheNodes), + alloc: ds.alloc.Fork().Reset(), + + // store configuration + pkgGetter: ds.pkgGetter, + cacheNativeTypes: ds.cacheNativeTypes, + pkgInjector: ds.pkgInjector, + nativeStore: ds.nativeStore, + go2gnoStrict: ds.go2gnoStrict, + + // transient + current: nil, + opslog: nil, + } + ds2.SetCachePackage(Uverse()) + + return transactionStore{ds2} +} + +type transactionStore struct{ *defaultStore } + +func (t transactionStore) Write() { + t.cacheTypes.(txlog.MapCommitter[TypeID, Type]).Commit() + t.cacheNodes.(txlog.MapCommitter[Location, BlockNode]).Commit() +} + +func (transactionStore) SetPackageGetter(pg PackageGetter) { + panic("SetPackageGetter may not be called in a transaction store") +} + +func (transactionStore) ClearCache() { + panic("ClearCache may not be called in a transaction store") +} + +// XXX: we should block Go2GnoType, because it uses a global cache map; +// but it's called during preprocess and thus breaks some testing code. +// let's wait until we remove Go2Gno entirely. +// https://github.com/gnolang/gno/issues/1361 +// func (transactionStore) Go2GnoType(reflect.Type) Type { +// panic("Go2GnoType may not be called in a transaction store") +// } + +func (transactionStore) SetPackageInjector(inj PackageInjector) { + panic("SetPackageInjector may not be called in a transaction store") +} + +func (transactionStore) SetNativeStore(ns NativeStore) { + panic("SetNativeStore may not be called in a transaction store") +} + +func (transactionStore) SetStrictGo2GnoMapping(strict bool) { + panic("SetStrictGo2GnoMapping may not be called in a transaction store") +} + // 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 CopyFromCachedStore(destStore, cachedStore Store, cachedBase, cachedIavl store.Store) { + ds, ss := destStore.(transactionStore), cachedStore.(*defaultStore) + + iter := cachedBase.Iterator(nil, nil) + for ; iter.Valid(); iter.Next() { + ds.baseStore.Set(iter.Key(), iter.Value()) + } + iter = cachedIavl.Iterator(nil, nil) + for ; iter.Valid(); iter.Next() { + ds.iavlStore.Set(iter.Key(), iter.Value()) + } + + ss.cacheTypes.Iterate()(func(k TypeID, v Type) bool { + ds.cacheTypes.Set(k, v) + return true + }) + ss.cacheNodes.Iterate()(func(k Location, v BlockNode) bool { + ds.cacheNodes.Set(k, v) + return true + }) } func (ds *defaultStore) GetAllocator() *Allocator { @@ -399,7 +504,6 @@ func (ds *defaultStore) DelObject(oo Object) { func (ds *defaultStore) GetType(tid TypeID) Type { tt := ds.GetTypeSafe(tid) if tt == nil { - ds.Print() panic(fmt.Sprintf("unexpected type with id %s", tid.String())) } return tt @@ -407,7 +511,7 @@ func (ds *defaultStore) GetType(tid TypeID) Type { func (ds *defaultStore) GetTypeSafe(tid TypeID) Type { // check cache. - if tt, exists := ds.cacheTypes[tid]; exists { + if tt, exists := ds.cacheTypes.Get(tid); exists { return tt } // check backend. @@ -424,7 +528,7 @@ func (ds *defaultStore) GetTypeSafe(tid TypeID) Type { } } // set in cache. - ds.cacheTypes[tid] = tt + ds.cacheTypes.Set(tid, tt) // after setting in cache, fill tt. fillType(ds, tt) return tt @@ -435,7 +539,7 @@ func (ds *defaultStore) GetTypeSafe(tid TypeID) Type { func (ds *defaultStore) SetCacheType(tt Type) { tid := tt.TypeID() - if tt2, exists := ds.cacheTypes[tid]; exists { + if tt2, exists := ds.cacheTypes.Get(tid); exists { if tt != tt2 { // NOTE: not sure why this would happen. panic("should not happen") @@ -443,14 +547,14 @@ func (ds *defaultStore) SetCacheType(tt Type) { // already set. } } else { - ds.cacheTypes[tid] = tt + ds.cacheTypes.Set(tid, tt) } } func (ds *defaultStore) SetType(tt Type) { tid := tt.TypeID() // return if tid already known. - if tt2, exists := ds.cacheTypes[tid]; exists { + if tt2, exists := ds.cacheTypes.Get(tid); exists { if tt != tt2 { // this can happen for a variety of reasons. // TODO classify them and optimize. @@ -465,7 +569,7 @@ func (ds *defaultStore) SetType(tt Type) { ds.baseStore.Set([]byte(key), bz) } // save type to cache. - ds.cacheTypes[tid] = tt + ds.cacheTypes.Set(tid, tt) } func (ds *defaultStore) GetBlockNode(loc Location) BlockNode { @@ -478,7 +582,7 @@ func (ds *defaultStore) GetBlockNode(loc Location) BlockNode { func (ds *defaultStore) GetBlockNodeSafe(loc Location) BlockNode { // check cache. - if bn, exists := ds.cacheNodes[loc]; exists { + if bn, exists := ds.cacheNodes.Get(loc); exists { return bn } // check backend. @@ -494,7 +598,7 @@ func (ds *defaultStore) GetBlockNodeSafe(loc Location) BlockNode { loc, bn.GetLocation())) } } - ds.cacheNodes[loc] = bn + ds.cacheNodes.Set(loc, bn) return bn } } @@ -513,7 +617,7 @@ func (ds *defaultStore) SetBlockNode(bn BlockNode) { // ds.backend.Set([]byte(key), bz) } // save node to cache. - ds.cacheNodes[loc] = bn + ds.cacheNodes.Set(loc, bn) // XXX duplicate? // XXX } @@ -582,6 +686,7 @@ func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage } return nil } + var memPkg *std.MemPackage amino.MustUnmarshal(bz, &memPkg) return memPkg @@ -637,47 +742,6 @@ func (ds *defaultStore) ClearObjectCache() { ds.SetCachePackage(Uverse()) } -// Unstable. -// This function is used to handle queries and checktx transactions. -func (ds *defaultStore) Fork() Store { - ds2 := &defaultStore{ - alloc: ds.alloc.Fork().Reset(), - - // Re-initialize caches. Some are cloned for speed. - cacheObjects: make(map[ObjectID]Object), - cacheTypes: maps.Clone(ds.cacheTypes), - // XXX: This is bad to say the least (ds.cacheNodes is shared with a - // child Store); however, cacheNodes is _not_ a cache, but a proper - // data store instead. SetBlockNode does not write anything to - // the underlying baseStore, and cloning this map makes everything run - // 4x slower, so here we are, copying the reference. - cacheNodes: ds.cacheNodes, - cacheNativeTypes: maps.Clone(ds.cacheNativeTypes), - - // baseStore and iavlStore should generally be changed using SwapStores. - baseStore: ds.baseStore, - iavlStore: ds.iavlStore, - - // native injections / store "config" - pkgGetter: ds.pkgGetter, - pkgInjector: ds.pkgInjector, - nativeStore: ds.nativeStore, - go2gnoStrict: ds.go2gnoStrict, - - // reset opslog and current. - opslog: nil, - current: nil, - } - ds2.SetCachePackage(Uverse()) - return ds2 -} - -// TODO: consider a better/faster/simpler way of achieving the overall same goal? -func (ds *defaultStore) SwapStores(baseStore, iavlStore store.Store) { - ds.baseStore = baseStore - ds.iavlStore = iavlStore -} - func (ds *defaultStore) SetPackageInjector(inj PackageInjector) { ds.pkgInjector = inj } @@ -693,18 +757,6 @@ 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() { - ds.baseStore.(types.Flusher).Flush() - ds.iavlStore.(types.Flusher).Flush() -} - // ---------------------------------------- // StoreOp @@ -775,8 +827,8 @@ func (ds *defaultStore) LogSwitchRealm(rlmpath string) { func (ds *defaultStore) ClearCache() { ds.cacheObjects = make(map[ObjectID]Object) - ds.cacheTypes = make(map[TypeID]Type) - ds.cacheNodes = make(map[Location]BlockNode) + ds.cacheTypes = txlog.GoMap[TypeID, Type](map[TypeID]Type{}) + ds.cacheNodes = txlog.GoMap[Location, BlockNode](map[Location]BlockNode{}) ds.cacheNativeTypes = make(map[reflect.Type]Type) // restore builtin types to cache. InitStoreCaches(ds) @@ -792,16 +844,18 @@ func (ds *defaultStore) Print() { utils.Print(ds.iavlStore) fmt.Println(colors.Yellow("//----------------------------------------")) fmt.Println(colors.Green("defaultStore:cacheTypes...")) - for tid, typ := range ds.cacheTypes { + ds.cacheTypes.Iterate()(func(tid TypeID, typ Type) bool { fmt.Printf("- %v: %v\n", tid, stringz.TrimN(fmt.Sprintf("%v", typ), 50)) - } + return true + }) fmt.Println(colors.Yellow("//----------------------------------------")) fmt.Println(colors.Green("defaultStore:cacheNodes...")) - for loc, bn := range ds.cacheNodes { + ds.cacheNodes.Iterate()(func(loc Location, bn BlockNode) bool { fmt.Printf("- %v: %v\n", loc, stringz.TrimN(fmt.Sprintf("%v", bn), 50)) - } + return true + }) fmt.Println(colors.Red("//----------------------------------------")) } diff --git a/gnovm/pkg/gnolang/store_test.go b/gnovm/pkg/gnolang/store_test.go new file mode 100644 index 00000000000..8114291d1b6 --- /dev/null +++ b/gnovm/pkg/gnolang/store_test.go @@ -0,0 +1,99 @@ +package gnolang + +import ( + "io" + "testing" + + "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/store/dbadapter" + storetypes "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/stretchr/testify/assert" +) + +func TestTransactionStore(t *testing.T) { + db := memdb.NewMemDB() + tm2Store := dbadapter.StoreConstructor(db, storetypes.StoreOptions{}) + + st := NewStore(nil, tm2Store, tm2Store) + wrappedTm2Store := tm2Store.CacheWrap() + txSt := st.BeginTransaction(wrappedTm2Store, wrappedTm2Store) + m := NewMachineWithOptions(MachineOptions{ + PkgPath: "hello", + Store: txSt, + Output: io.Discard, + }) + _, pv := m.RunMemPackage(&std.MemPackage{ + Name: "hello", + Path: "hello", + Files: []*std.MemFile{ + {Name: "hello.gno", Body: "package hello; func main() { println(A(11)); }; type A int"}, + }, + }, true) + m.SetActivePackage(pv) + m.RunMain() + + // mem package should only exist in txSt + // (check both memPackage and types - one is stored directly in the db, + // the other uses txlog) + assert.Nil(t, st.GetMemPackage("hello")) + assert.NotNil(t, txSt.GetMemPackage("hello")) + assert.PanicsWithValue(t, "unexpected type with id hello.A", func() { st.GetType("hello.A") }) + assert.NotNil(t, txSt.GetType("hello.A")) + + // use write on the stores + txSt.Write() + wrappedTm2Store.Write() + + // mem package should exist and be ==. + res := st.GetMemPackage("hello") + assert.NotNil(t, res) + assert.Equal(t, txSt.GetMemPackage("hello"), res) + helloA := st.GetType("hello.A") + assert.NotNil(t, helloA) + assert.Equal(t, txSt.GetType("hello.A"), helloA) +} + +func TestTransactionStore_blockedMethods(t *testing.T) { + // These methods should panic as they modify store settings, which should + // only be changed in the root store. + assert.Panics(t, func() { transactionStore{}.SetPackageGetter(nil) }) + assert.Panics(t, func() { transactionStore{}.ClearCache() }) + assert.Panics(t, func() { transactionStore{}.SetPackageInjector(nil) }) + assert.Panics(t, func() { transactionStore{}.SetNativeStore(nil) }) + assert.Panics(t, func() { transactionStore{}.SetStrictGo2GnoMapping(false) }) +} + +func TestCopyFromCachedStore(t *testing.T) { + // Create cached store, with a type and a mempackage. + c1 := memdb.NewMemDB() + c1s := dbadapter.StoreConstructor(c1, storetypes.StoreOptions{}) + c2 := memdb.NewMemDB() + c2s := dbadapter.StoreConstructor(c2, storetypes.StoreOptions{}) + cachedStore := NewStore(nil, c1s, c2s) + cachedStore.SetType(&DeclaredType{ + PkgPath: "io", + Name: "Reader", + Base: BoolType, + }) + cachedStore.AddMemPackage(&std.MemPackage{ + Name: "math", + Path: "math", + Files: []*std.MemFile{ + {Name: "math.gno", Body: "package math"}, + }, + }) + + // Create dest store and copy. + d1, d2 := memdb.NewMemDB(), memdb.NewMemDB() + d1s := dbadapter.StoreConstructor(d1, storetypes.StoreOptions{}) + d2s := dbadapter.StoreConstructor(d2, storetypes.StoreOptions{}) + destStore := NewStore(nil, d1s, d2s) + destStoreTx := destStore.BeginTransaction(nil, nil) // CopyFromCachedStore requires a tx store. + CopyFromCachedStore(destStoreTx, cachedStore, c1s, c2s) + destStoreTx.Write() + + assert.Equal(t, c1, d1, "cached baseStore and dest baseStore should match") + assert.Equal(t, c2, d2, "cached iavlStore and dest iavlStore should match") + assert.Equal(t, cachedStore.cacheTypes, destStore.cacheTypes, "cacheTypes should match") +} 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 index 870eb10b690..31025fef152 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,70 @@ func assertAssignableTo(xt, dt Type, autoNative bool) { } } +// checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt. +func checkValDefineMismatch(n Node) { + var ( + valueDecl *ValueDecl + assign *AssignStmt + values []Expr + numNames int + numValues int + ) + + switch x := n.(type) { + case *ValueDecl: + valueDecl = x + numNames = len(valueDecl.NameExprs) + numValues = len(valueDecl.Values) + values = valueDecl.Values + case *AssignStmt: + if x.Op != DEFINE { + return + } + + assign = x + numNames = len(assign.Lhs) + numValues = len(assign.Rhs) + values = assign.Rhs + default: + panic(fmt.Sprintf("unexpected node type %T", n)) + } + + if numValues == 0 || numValues == numNames { + return + } + + // Special case for single value. + // If the value is a call expression, type assertion, or index expression, + // it can be assigned to multiple variables. + if numValues == 1 { + switch values[0].(type) { + case *CallExpr: + return + case *TypeAssertExpr: + if numNames != 2 { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues)) + } + return + case *IndexExpr: + if numNames != 2 { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues)) + } + return + } + } + + if valueDecl != nil { + if numNames > numValues { + panic(fmt.Sprintf("missing init expr for %s", valueDecl.NameExprs[numValues].String())) + } + + panic(fmt.Sprintf("extra init expr %s", values[numNames].String())) + } + + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues)) +} + // 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. @@ -237,14 +301,15 @@ func checkAssignableTo(xt, dt Type, autoNative bool) error { 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 idt.IsImplementedBy(xt) { + } 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 does not implement %s (%s)", xt.String(), - dt.String()) + dt.String(), + err.Error()) } } else if ndt, ok := baseOf(dt).(*NativeType); ok { nidt := ndt.Type diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index ab8e9effdc8..b43f623ea99 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1009,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 } @@ -1023,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. @@ -1033,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 { diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 880a75396ca..d62d4228d68 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -193,32 +193,32 @@ func UverseNode() *PackageNode { return } else if arg0Type.Elem().Kind() == Uint8Kind { // append(nil, *SliceValue) new data bytes --- - data := make([]byte, arg1Length) + arrayValue := m.Alloc.NewDataArray(arg1Length) if arg1Base.Data == nil { copyListToData( - data[:arg1Length], + arrayValue.Data[:arg1Length], arg1Base.List[arg1Offset:arg1EndIndex]) } else { copy( - data[:arg1Length], + arrayValue.Data[:arg1Length], arg1Base.Data[arg1Offset:arg1EndIndex]) } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromData(data), + V: m.Alloc.NewSlice(arrayValue, 0, arg1Length, arg1Length), }) return } else { // append(nil, *SliceValue) new list --------- - list := make([]TypedValue, arg1Length) - if 0 < arg1Length { + arrayValue := m.Alloc.NewListArray(arg1Length) + if arg1Length > 0 { for i := 0; i < arg1Length; i++ { - list[i] = arg1Base.List[arg1Offset+i].unrefCopy(m.Alloc, m.Store) + arrayValue.List[i] = arg1Base.List[arg1Offset+i].unrefCopy(m.Alloc, m.Store) } } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromList(list), + V: m.Alloc.NewSlice(arrayValue, 0, arg1Length, arg1Length), }) return } @@ -236,27 +236,27 @@ func UverseNode() *PackageNode { return } else if arg0Type.Elem().Kind() == Uint8Kind { // append(nil, *NativeValue) new data bytes -- - data := make([]byte, arg1NativeValueLength) + arrayValue := m.Alloc.NewDataArray(arg1NativeValueLength) copyNativeToData( - data[:arg1NativeValueLength], + arrayValue.Data[:arg1NativeValueLength], arg1NativeValue, arg1NativeValueLength) m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromData(data), + V: m.Alloc.NewSlice(arrayValue, 0, arg1NativeValueLength, arg1NativeValueLength), }) return } else { // append(nil, *NativeValue) new list -------- - list := make([]TypedValue, arg1NativeValueLength) - if 0 < arg1NativeValueLength { + arrayValue := m.Alloc.NewListArray(arg1NativeValueLength) + if arg1NativeValueLength > 0 { copyNativeToList( m.Alloc, - list[:arg1NativeValueLength], + arrayValue.List[:arg1NativeValueLength], arg1NativeValue, arg1NativeValueLength) } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromList(list), + V: m.Alloc.NewSlice(arrayValue, 0, arg1NativeValueLength, arg1NativeValueLength), }) return } @@ -344,55 +344,57 @@ func UverseNode() *PackageNode { } } else if arg0Type.Elem().Kind() == Uint8Kind { // append(*SliceValue, *SliceValue) new data bytes --- - data := make([]byte, arg0Length+arg1Length) + newLength := arg0Length + arg1Length + arrayValue := m.Alloc.NewDataArray(newLength) if 0 < arg0Length { if arg0Base.Data == nil { copyListToData( - data[:arg0Length], + arrayValue.Data[:arg0Length], arg0Base.List[arg0Offset:arg0Offset+arg0Length]) } else { copy( - data[:arg0Length], + arrayValue.Data[:arg0Length], arg0Base.Data[arg0Offset:arg0Offset+arg0Length]) } } if 0 < arg1Length { if arg1Base.Data == nil { copyListToData( - data[arg0Length:arg0Length+arg1Length], + arrayValue.Data[arg0Length:newLength], arg1Base.List[arg1Offset:arg1Offset+arg1Length]) } else { copy( - data[arg0Length:arg0Length+arg1Length], + arrayValue.Data[arg0Length:newLength], arg1Base.Data[arg1Offset:arg1Offset+arg1Length]) } } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromData(data), + V: m.Alloc.NewSlice(arrayValue, 0, newLength, newLength), }) return } else { // append(*SliceValue, *SliceValue) new list --------- - list := make([]TypedValue, arg0Length+arg1Length) - if 0 < arg0Length { + arrayLen := arg0Length + arg1Length + arrayValue := m.Alloc.NewListArray(arrayLen) + if arg0Length > 0 { if arg0Base.Data == nil { for i := 0; i < arg0Length; i++ { - list[i] = arg0Base.List[arg0Offset+i].unrefCopy(m.Alloc, m.Store) + arrayValue.List[i] = arg0Base.List[arg0Offset+i].unrefCopy(m.Alloc, m.Store) } } else { panic("should not happen") } } - if 0 < arg1Length { + if arg1Length > 0 { if arg1Base.Data == nil { for i := 0; i < arg1Length; i++ { - list[arg0Length+i] = arg1Base.List[arg1Offset+i].unrefCopy(m.Alloc, m.Store) + arrayValue.List[arg0Length+i] = arg1Base.List[arg1Offset+i].unrefCopy(m.Alloc, m.Store) } } else { copyDataToList( - list[arg0Length:arg0Length+arg1Length], + arrayValue.List[arg0Length:arg0Length+arg1Length], arg1Base.Data[arg1Offset:arg1Offset+arg1Length], arg1Type.Elem(), ) @@ -400,7 +402,7 @@ func UverseNode() *PackageNode { } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromList(list), + V: m.Alloc.NewSlice(arrayValue, 0, arrayLen, arrayLen), }) return } @@ -441,46 +443,47 @@ func UverseNode() *PackageNode { } } else if arg0Type.Elem().Kind() == Uint8Kind { // append(*SliceValue, *NativeValue) new data bytes -- - data := make([]byte, arg0Length+arg1NativeValueLength) + newLength := arg0Length + arg1NativeValueLength + arrayValue := m.Alloc.NewDataArray(newLength) if 0 < arg0Length { if arg0Base.Data == nil { copyListToData( - data[:arg0Length], + arrayValue.Data[:arg0Length], arg0Base.List[arg0Offset:arg0Offset+arg0Length]) } else { copy( - data[:arg0Length], + arrayValue.Data[:arg0Length], arg0Base.Data[arg0Offset:arg0Offset+arg0Length]) } } if 0 < arg1NativeValueLength { copyNativeToData( - data[arg0Length:arg0Length+arg1NativeValueLength], + arrayValue.Data[arg0Length:newLength], arg1NativeValue, arg1NativeValueLength) } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromData(data), + V: m.Alloc.NewSlice(arrayValue, 0, newLength, newLength), }) return } else { // append(*SliceValue, *NativeValue) new list -------- listLen := arg0Length + arg1NativeValueLength - list := make([]TypedValue, listLen) + arrayValue := m.Alloc.NewListArray(listLen) if 0 < arg0Length { for i := 0; i < listLen; i++ { - list[i] = arg0Base.List[arg0Offset+i].unrefCopy(m.Alloc, m.Store) + arrayValue.List[i] = arg0Base.List[arg0Offset+i].unrefCopy(m.Alloc, m.Store) } } if 0 < arg1NativeValueLength { copyNativeToList( m.Alloc, - list[arg0Length:listLen], + arrayValue.List[arg0Length:listLen], arg1NativeValue, arg1NativeValueLength) } m.PushValue(TypedValue{ T: arg0Type, - V: m.Alloc.NewSliceFromList(list), + V: m.Alloc.NewSlice(arrayValue, 0, listLen, listLen), }) return } @@ -779,25 +782,25 @@ func UverseNode() *PackageNode { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() li := lv.ConvertGetInt() if et.Kind() == Uint8Kind { - data := make([]byte, li) + arrayValue := m.Alloc.NewDataArray(li) m.PushValue(TypedValue{ T: tt, - V: m.Alloc.NewSliceFromData(data), + V: m.Alloc.NewSlice(arrayValue, 0, li, li), }) return } else { - list := make([]TypedValue, li) + arrayValue := m.Alloc.NewListArray(li) if et.Kind() == InterfaceKind { // leave as is } else { // init zero elements with concrete type. for i := 0; i < li; i++ { - list[i] = defaultTypedValue(m.Alloc, et) + arrayValue.List[i] = defaultTypedValue(m.Alloc, et) } } m.PushValue(TypedValue{ T: tt, - V: m.Alloc.NewSliceFromList(list), + V: m.Alloc.NewSlice(arrayValue, 0, li, li), }) return } @@ -807,30 +810,37 @@ func UverseNode() *PackageNode { cv := vargs.TV.GetPointerAtIndexInt(m.Store, 1).Deref() ci := cv.ConvertGetInt() if et.Kind() == Uint8Kind { - data := make([]byte, li, ci) + arrayValue := m.Alloc.NewDataArray(ci) m.PushValue(TypedValue{ T: tt, - V: m.Alloc.NewSliceFromData(data), + V: m.Alloc.NewSlice(arrayValue, 0, li, ci), }) return } else { - list := make([]TypedValue, li, ci) + arrayValue := m.Alloc.NewListArray(ci) if et := bt.Elem(); et.Kind() == InterfaceKind { // leave as is } else { - // init zero elements with concrete type. - // the elements beyond len l within cap c - // must also be initialized, for a future - // slice operation may refer to them. - // XXX can this be removed? - list2 := list[:ci] + // Initialize all elements within capacity with default + // type values. These need to be initialized because future + // slice operations could get messy otherwise. Simple capacity + // expansions like `a = a[:cap(a)]` would make it trivial to + // initialize zero values at the time of the slice operation. + // But sequences of operations like: + // a := make([]int, 1, 10) + // a = a[7:cap(a)] + // a = a[3:5] + // + // require a bit more work to handle correctly, requiring that + // all new TypedValue slice elements be checked to ensure they have + // a value for every slice operation, which is not desirable. for i := 0; i < ci; i++ { - list2[i] = defaultTypedValue(m.Alloc, et) + arrayValue.List[i] = defaultTypedValue(m.Alloc, et) } } m.PushValue(TypedValue{ T: tt, - V: m.Alloc.NewSliceFromList(list), + V: m.Alloc.NewSlice(arrayValue, 0, li, ci), }) return } @@ -928,17 +938,7 @@ func UverseNode() *PackageNode { return }, ) - defNative("panic", - Flds( // params - "err", AnyT(), // args[0] - ), - nil, // results - func(m *Machine) { - arg0 := m.LastBlock().GetParams1() - xv := arg0.Deref() - panic(xv.Sprint(m)) - }, - ) + // NOTE: panic is its own statement type, and is not defined as a function. defNative("print", Flds( // params "xs", Vrd(AnyT()), // args[0] diff --git a/gnovm/pkg/gnolang/uverse_test.go b/gnovm/pkg/gnolang/uverse_test.go index 7a6c0567e45..76961b70ccb 100644 --- a/gnovm/pkg/gnolang/uverse_test.go +++ b/gnovm/pkg/gnolang/uverse_test.go @@ -4,14 +4,14 @@ import ( "testing" ) -type printlnTestCases struct { +type uverseTestCases struct { name string code string expected string } func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) { - test := []printlnTestCases{ + test := []uverseTestCases{ { name: "print empty slice", code: `package test diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 9b44a3c71e7..bbf77bf19c7 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -601,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()) @@ -2116,13 +2125,18 @@ func (tv *TypedValue) GetLength() int { switch bt := baseOf(tv.T).(type) { case PrimitiveType: if bt != StringType { - panic("should not happen") + panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) } return 0 case *ArrayType: return bt.Len case *SliceType: return 0 + case *PointerType: + if at, ok := bt.Elt.(*ArrayType); ok { + return at.Len + } + panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) default: panic(fmt.Sprintf( "unexpected type for len(): %s", @@ -2140,6 +2154,11 @@ func (tv *TypedValue) GetLength() int { return cv.GetLength() case *NativeValue: return cv.Value.Len() + case PointerValue: + if av, ok := cv.TV.V.(*ArrayValue); ok { + return av.GetLength() + } + panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) default: panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) @@ -2148,27 +2167,34 @@ func (tv *TypedValue) GetLength() int { func (tv *TypedValue) GetCapacity() int { if tv.V == nil { - if debug { - // assert acceptable type. - switch baseOf(tv.T).(type) { - // strings have no capacity. - case *ArrayType: - case *SliceType: - default: - panic("should not happen") + // assert acceptable type. + switch bt := baseOf(tv.T).(type) { + // strings have no capacity. + case *ArrayType: + return bt.Len + case *SliceType: + return 0 + case *PointerType: + if at, ok := bt.Elt.(*ArrayType); ok { + return at.Len } + panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) + default: + panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) } - return 0 } switch cv := tv.V.(type) { - case StringValue: - return len(string(cv)) case *ArrayValue: return cv.GetCapacity() case *SliceValue: return cv.GetCapacity() case *NativeValue: return cv.Value.Cap() + case PointerValue: + if av, ok := cv.TV.V.(*ArrayValue); ok { + return av.GetCapacity() + } + panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) default: panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) @@ -2191,13 +2217,13 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue { "invalid slice index %d > %d", low, high)) } - if tv.GetCapacity() < high { - panic(fmt.Sprintf( - "slice bounds out of range [%d:%d] with capacity %d", - low, high, tv.GetCapacity())) - } switch t := baseOf(tv.T).(type) { case PrimitiveType: + if tv.GetLength() < high { + panic(fmt.Sprintf( + "slice bounds out of range [%d:%d] with string length %d", + low, high, tv.GetLength())) + } if t == StringType || t == UntypedStringType { return TypedValue{ T: tv.T, @@ -2206,6 +2232,11 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue { } panic("non-string primitive type cannot be sliced") case *ArrayType: + if tv.GetLength() < high { + panic(fmt.Sprintf( + "slice bounds out of range [%d:%d] with array length %d", + low, high, tv.GetLength())) + } av := tv.V.(*ArrayValue) st := alloc.NewType(&SliceType{ Elt: t.Elt, @@ -2221,6 +2252,11 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue { ), } case *SliceType: + if tv.GetCapacity() < high { + panic(fmt.Sprintf( + "slice bounds out of range [%d:%d] with capacity %d", + low, high, tv.GetCapacity())) + } if tv.V == nil { if low != 0 || high != 0 { panic("nil slice index out of range") diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 204fab62c86..a414f440e4e 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -170,6 +170,9 @@ func (fv *FuncValue) String() string { if fv.Type == nil { return fmt.Sprintf("incomplete-func ?%s(?)?", name) } + if name == "" { + return fmt.Sprintf("%s{...}", fv.Type.String()) + } return name } diff --git a/gnovm/pkg/gnolang/values_test.go b/gnovm/pkg/gnolang/values_test.go new file mode 100644 index 00000000000..ce6edd0a2f9 --- /dev/null +++ b/gnovm/pkg/gnolang/values_test.go @@ -0,0 +1,70 @@ +package gnolang + +import ( + "fmt" + "testing" +) + +type mockTypedValueStruct struct { + field int +} + +func (m *mockTypedValueStruct) assertValue() {} + +func (m *mockTypedValueStruct) String() string { + return fmt.Sprintf("MockTypedValueStruct(%d)", m.field) +} + +func TestGetLengthPanic(t *testing.T) { + tests := []struct { + name string + tv TypedValue + expected string + }{ + { + name: "NonArrayPointer", + tv: TypedValue{ + T: &PointerType{Elt: &StructType{}}, + V: PointerValue{ + TV: &TypedValue{ + T: &StructType{}, + V: &mockTypedValueStruct{field: 42}, + }, + }, + }, + expected: "unexpected type for len(): *struct{}", + }, + { + name: "UnexpectedType", + tv: TypedValue{ + T: &StructType{}, + V: &mockTypedValueStruct{field: 42}, + }, + expected: "unexpected type for len(): struct{}", + }, + { + name: "UnexpectedPointerType", + tv: TypedValue{ + T: &PointerType{Elt: &StructType{}}, + V: nil, + }, + expected: "unexpected type for len(): *struct{}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } else { + if r != tt.expected { + t.Errorf("expected panic message to be %q, got %q", tt.expected, r) + } + } + }() + + tt.tv.GetLength() + }) + } +} 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/tests/file.go b/gnovm/tests/file.go index 347a61ec8ee..f6bd789f1bf 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" @@ -53,7 +54,7 @@ func TestContext(pkgPath string, send std.Coins) *teststd.TestExecContext { pkgAddr := gno.DerivePkgAddr(pkgPath) // the addr of the pkgPath called. caller := gno.DerivePkgAddr("user1.gno") - pkgCoins := std.MustParseCoins("200000000ugnot").Add(send) // >= send. + pkgCoins := std.MustParseCoins(ugnot.ValueString(200000000)).Add(send) // >= send. banker := newTestBanker(pkgAddr.Bech32(), pkgCoins) ctx := stdlibs.ExecContext{ ChainID: "dev", @@ -109,7 +110,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { opt(&f) } - directives, pkgPath, resWanted, errWanted, rops, maxAlloc, send := wantedFromComment(path) + directives, pkgPath, resWanted, errWanted, rops, stacktraceWanted, maxAlloc, send := wantedFromComment(path) if pkgPath == "" { pkgPath = "main" } @@ -124,6 +125,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { store := TestStore(rootDir, "./files", stdin, stdout, stderr, mode) store.SetLogStoreOps(true) m := testMachineCustom(store, pkgPath, stdout, maxAlloc, send) + checkMachineIsEmpty := true // TODO support stdlib groups, but make testing safe; // e.g. not be able to make network connections. @@ -259,6 +261,8 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { errstr = v.Sprint(m) case *gno.PreprocessError: errstr = v.Unwrap().Error() + case gno.UnhandledPanicError: + errstr = v.Error() default: errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) } @@ -279,7 +283,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { // NOTE: ignores any gno.GetDebugErrors(). gno.ClearDebugErrors() - return nil // nothing more to do. + checkMachineIsEmpty = false // nothing more to do. } else { // record errors when errWanted is empty and pnc not nil if pnc != nil { @@ -307,6 +311,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { panic(fmt.Sprintf("fail on %s: got unexpected debug error(s): %v", path, gno.GetDebugErrors())) } // pnc is nil, errWanted empty, no gno debug errors + checkMachineIsEmpty = false } case "Output": // panic if got unexpected error @@ -372,24 +377,51 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { } } } + case "Stacktrace": + if stacktraceWanted != "" { + var stacktrace string + + switch pnc.(type) { + case gno.UnhandledPanicError: + stacktrace = m.ExceptionsStacktrace() + default: + stacktrace = m.Stacktrace().String() + } + + if !strings.Contains(stacktrace, stacktraceWanted) { + diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(stacktraceWanted), + B: difflib.SplitLines(stacktrace), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 1, + }) + panic(fmt.Sprintf("fail on %s: diff:\n%s\n", path, diff)) + } + } + checkMachineIsEmpty = false default: - return nil + checkMachineIsEmpty = false } } } - // Check that machine is empty. - err = m.CheckEmpty() - if err != nil { - if f.logger != nil { - f.logger("last state: \n", m.String()) + if checkMachineIsEmpty { + // Check that machine is empty. + err = m.CheckEmpty() + if err != nil { + if f.logger != nil { + f.logger("last state: \n", m.String()) + } + panic(fmt.Sprintf("fail on %s: machine not empty after main: %v", path, err)) } - panic(fmt.Sprintf("fail on %s: machine not empty after main: %v", path, err)) } return nil } -func wantedFromComment(p string) (directives []string, pkgPath, res, err, rops string, maxAlloc int64, send std.Coins) { +func wantedFromComment(p string) (directives []string, pkgPath, res, err, rops, stacktrace string, maxAlloc int64, send std.Coins) { fset := token.NewFileSet() f, err2 := parser.ParseFile(fset, p, nil, parser.ParseComments) if err2 != nil { @@ -431,6 +463,10 @@ func wantedFromComment(p string) (directives []string, pkgPath, res, err, rops s rops = strings.TrimPrefix(text, "Realm:\n") rops = strings.TrimSpace(rops) directives = append(directives, "Realm") + } else if strings.HasPrefix(text, "Stacktrace:\n") { + stacktrace = strings.TrimPrefix(text, "Stacktrace:\n") + stacktrace = strings.TrimSpace(stacktrace) + directives = append(directives, "Stacktrace") } else { // ignore unexpected. } diff --git a/gnovm/tests/files/a31.gno b/gnovm/tests/files/a31.gno index d687b098e7c..052ab673cf2 100644 --- a/gnovm/tests/files/a31.gno +++ b/gnovm/tests/files/a31.gno @@ -2,10 +2,10 @@ package main func main() { for range []int{0, 1, 2} { - print("hello ") + print("hello,") } println("") } // Output: -// hello hello hello +// hello,hello,hello, diff --git a/gnovm/tests/files/access6_stdlibs.gno b/gnovm/tests/files/access6_stdlibs.gno index 57b3d63d1a6..443f2f5291d 100644 --- a/gnovm/tests/files/access6_stdlibs.gno +++ b/gnovm/tests/files/access6_stdlibs.gno @@ -16,4 +16,4 @@ func main() { } // Error: -// main/files/access6_stdlibs.gno:15:2: 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_stdlibs.gno b/gnovm/tests/files/access7_stdlibs.gno index 6ba10780856..01c9ed83fa0 100644 --- a/gnovm/tests/files/access7_stdlibs.gno +++ b/gnovm/tests/files/access7_stdlibs.gno @@ -20,4 +20,4 @@ func main() { } // Error: -// main/files/access7_stdlibs.gno:19:2: 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/assign24.gno b/gnovm/tests/files/assign24.gno new file mode 100644 index 00000000000..408258def92 --- /dev/null +++ b/gnovm/tests/files/assign24.gno @@ -0,0 +1,8 @@ +package main + +func main() { + a, b := 1 +} + +// Error: +// main/files/assign24.gno:4:2: assignment mismatch: 2 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/assign25.gno b/gnovm/tests/files/assign25.gno new file mode 100644 index 00000000000..b945afc3b1f --- /dev/null +++ b/gnovm/tests/files/assign25.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + a, b, c := 2, foo() + + println(a, b, c) +} + +// Error: +// main/files/assign25.gno:8:2: assignment mismatch: 3 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/assign25b.gno b/gnovm/tests/files/assign25b.gno new file mode 100644 index 00000000000..886777324f5 --- /dev/null +++ b/gnovm/tests/files/assign25b.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + a, b, c := 2, 3, 4, foo() + + println(a, b, c) +} + +// Error: +// main/files/assign25b.gno:8:2: assignment mismatch: 3 variable(s) but 4 value(s) diff --git a/gnovm/tests/files/assign26.gno b/gnovm/tests/files/assign26.gno new file mode 100644 index 00000000000..f974ae0b174 --- /dev/null +++ b/gnovm/tests/files/assign26.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var i interface{} = 1 + a, b, c := i.(int) +} + +// Error: +// main/files/assign26.gno:5:2: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/assign27.gno b/gnovm/tests/files/assign27.gno new file mode 100644 index 00000000000..14fc8bf4e37 --- /dev/null +++ b/gnovm/tests/files/assign27.gno @@ -0,0 +1,9 @@ +package main + +func main() { + s := []string{"1", "2"} + a, b, c := s[0] +} + +// Error: +// main/files/assign27.gno:5:2: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/assign28.gno b/gnovm/tests/files/assign28.gno new file mode 100644 index 00000000000..a0afe4f0e36 --- /dev/null +++ b/gnovm/tests/files/assign28.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + a, c := 1, 2, 3 + fmt.Println(a, c) +} + +// Error: +// main/files/assign28.gno:6:2: assignment mismatch: 2 variable(s) but 3 value(s) 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/unnamedtype5_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno index 583e2f12bd8..0c15ce1ae02 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno @@ -34,8 +34,8 @@ func main() { // Output: // 1 (inc main.op) -// 0 +// 0 func(n int)( int){...} // -1 dec -// 0 ( main.op) +// 0 (func(n int)( int){...} main.op) // -1 dec // 1 inc diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno index e14e64e4dfd..165dd8e18a4 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno @@ -27,6 +27,7 @@ func main() { } // Output: +// func(n int)( int){...} // 1 -// ( main.op) +// (func(n int)( int){...} main.op) // -1 diff --git a/gnovm/tests/files/cap1.gno b/gnovm/tests/files/cap1.gno new file mode 100644 index 00000000000..e382357ca11 --- /dev/null +++ b/gnovm/tests/files/cap1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + x := cap(&exp) + println(x) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/cap10.gno b/gnovm/tests/files/cap10.gno new file mode 100644 index 00000000000..a76c723f77a --- /dev/null +++ b/gnovm/tests/files/cap10.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println("cap", cap(struct{ A int }{})) +} + +// Error: +// unexpected type for cap(): struct{A int} diff --git a/gnovm/tests/files/cap2.gno b/gnovm/tests/files/cap2.gno new file mode 100644 index 00000000000..a834c17b474 --- /dev/null +++ b/gnovm/tests/files/cap2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + exp := [...]int{1, 2, 3, 4, 5} + println(cap(exp)) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/cap3.gno b/gnovm/tests/files/cap3.gno new file mode 100644 index 00000000000..c5b323338d8 --- /dev/null +++ b/gnovm/tests/files/cap3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + slice := make([]int, 3, 5) + println(cap(slice)) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/cap4.gno b/gnovm/tests/files/cap4.gno new file mode 100644 index 00000000000..758001358fa --- /dev/null +++ b/gnovm/tests/files/cap4.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var slice []int + println(cap(slice)) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/cap5.gno b/gnovm/tests/files/cap5.gno new file mode 100644 index 00000000000..ce2b6be2c42 --- /dev/null +++ b/gnovm/tests/files/cap5.gno @@ -0,0 +1,12 @@ +package main + +func main() { + printCap(nil) +} + +func printCap(arr *[2]int) { + println(cap(arr)) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/cap6.gno b/gnovm/tests/files/cap6.gno new file mode 100644 index 00000000000..182279b7ec6 --- /dev/null +++ b/gnovm/tests/files/cap6.gno @@ -0,0 +1,31 @@ +package main + +func main() { + var arr [5]int + var nilArr *[5]int + var nilSlice []int + var nilArr2 *[8]struct{ A, B int } + var nilMatrix *[2][3]int + + println("cap(arr): ", cap(arr)) + println("cap(&arr): ", cap(&arr)) + println("cap(nilArr): ", cap(nilArr)) + println("cap(nilSlice): ", cap(nilSlice)) + println("cap(nilArr2): ", cap(nilArr2)) + println("cap(nilMatrix):", cap(nilMatrix)) + + printCap(nil) +} + +func printCap(arr *[3]string) { + println("printCap: ", cap(arr)) +} + +// Output: +// cap(arr): 5 +// cap(&arr): 5 +// cap(nilArr): 5 +// cap(nilSlice): 0 +// cap(nilArr2): 8 +// cap(nilMatrix): 2 +// printCap: 3 diff --git a/gnovm/tests/files/cap7.gno b/gnovm/tests/files/cap7.gno new file mode 100644 index 00000000000..73e2f11c147 --- /dev/null +++ b/gnovm/tests/files/cap7.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var s string + println("cap", cap(s)) +} + +// Error: +// unexpected type for cap(): string diff --git a/gnovm/tests/files/cap8.gno b/gnovm/tests/files/cap8.gno new file mode 100644 index 00000000000..7fe9b48e28b --- /dev/null +++ b/gnovm/tests/files/cap8.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var i *int + println("cap", cap(i)) +} + +// Error: +// unexpected type for cap(): *int diff --git a/gnovm/tests/files/cap9.gno b/gnovm/tests/files/cap9.gno new file mode 100644 index 00000000000..b7aad6037b4 --- /dev/null +++ b/gnovm/tests/files/cap9.gno @@ -0,0 +1,9 @@ +package main + +func main() { + i := new(int) + println("cap", cap(i)) +} + +// Error: +// unexpected type for cap(): *int diff --git a/gnovm/tests/files/circular_constant.gno b/gnovm/tests/files/circular_constant.gno new file mode 100644 index 00000000000..ff25da7428d --- /dev/null +++ b/gnovm/tests/files/circular_constant.gno @@ -0,0 +1,10 @@ +package main + +const A = B +const B = A + 1 + +func main() { +} + +// Error: +// main/files/circular_constant.gno:3:7: constant definition loop with A diff --git a/gnovm/tests/files/convert4.gno b/gnovm/tests/files/convert4.gno index d7ab4905f62..e03e1d07ce3 100644 --- a/gnovm/tests/files/convert4.gno +++ b/gnovm/tests/files/convert4.gno @@ -4,4 +4,5 @@ func main() { println(int(nil)) } -// Error: cannot convert (undefined) to int +// Error: +// main/files/convert4.gno:4:10: cannot convert (undefined) to int diff --git a/gnovm/tests/files/convert5.gno b/gnovm/tests/files/convert5.gno index 74063709110..e2e16c5eb83 100644 --- a/gnovm/tests/files/convert5.gno +++ b/gnovm/tests/files/convert5.gno @@ -6,4 +6,5 @@ func main() { println(ints) } -// Error: cannot convert (undefined) to int +// Error: +// main/files/convert5.gno:3:1: cannot convert (undefined) to int diff --git a/gnovm/tests/files/defer6.gno b/gnovm/tests/files/defer6.gno index 65a8d933996..fa0d61285c3 100644 --- a/gnovm/tests/files/defer6.gno +++ b/gnovm/tests/files/defer6.gno @@ -1,21 +1,21 @@ package main func f1() { - defer print("f1-begin ") + defer print("f1-begin,") f2() - defer print("f1-end ") + defer print("f1-end,") } func f2() { - defer print("f2-begin ") + defer print("f2-begin,") f3() - defer print("f2-end ") + defer print("f2-end,") } func f3() { - defer print("f3-begin ") - print("hello ") - defer print("f3-end ") + defer print("f3-begin,") + print("hello,") + defer print("f3-end,") } func main() { @@ -24,4 +24,4 @@ func main() { } // Output: -// hello f3-end f3-begin f2-end f2-begin f1-end f1-begin +// hello,f3-end,f3-begin,f2-end,f2-begin,f1-end,f1-begin, diff --git a/gnovm/tests/files/fun27.gno b/gnovm/tests/files/fun27.gno index 8f4d6c02e5f..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:2: 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/len1.gno b/gnovm/tests/files/len1.gno new file mode 100644 index 00000000000..f627fba190f --- /dev/null +++ b/gnovm/tests/files/len1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + x := len(&exp) + println(x) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/len2.gno b/gnovm/tests/files/len2.gno new file mode 100644 index 00000000000..377ff01851f --- /dev/null +++ b/gnovm/tests/files/len2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + exp := [...]string{"HELLO", "WORLD"} + println(len(exp)) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/len3.gno b/gnovm/tests/files/len3.gno new file mode 100644 index 00000000000..89fe863b20b --- /dev/null +++ b/gnovm/tests/files/len3.gno @@ -0,0 +1,10 @@ +package main + +func main() { + exp := [...]int{1, 2, 3, 4, 5} + ptr := &exp + println(len(ptr)) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/len4.gno b/gnovm/tests/files/len4.gno new file mode 100644 index 00000000000..8b24c755041 --- /dev/null +++ b/gnovm/tests/files/len4.gno @@ -0,0 +1,12 @@ +package main + +func main() { + printLen(nil) +} + +func printLen(arr *[2]int) { + println(len(arr)) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/len5.gno b/gnovm/tests/files/len5.gno new file mode 100644 index 00000000000..7daf3c2cc07 --- /dev/null +++ b/gnovm/tests/files/len5.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var arr *[3]string + println(cap(arr)) +} + +// Output: +// 3 diff --git a/gnovm/tests/files/len6.gno b/gnovm/tests/files/len6.gno new file mode 100644 index 00000000000..9656f65e08d --- /dev/null +++ b/gnovm/tests/files/len6.gno @@ -0,0 +1,14 @@ +package main + +func main() { + printLenCap(nil) +} + +func printLenCap(arr *[4]float64) { + println(len(arr)) + println(cap(arr)) +} + +// Output: +// 4 +// 4 diff --git a/gnovm/tests/files/len7.gno b/gnovm/tests/files/len7.gno new file mode 100644 index 00000000000..5deccdbf331 --- /dev/null +++ b/gnovm/tests/files/len7.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(len(new(int))) +} + +// Error: +// unexpected type for len(): *int diff --git a/gnovm/tests/files/len8.gno b/gnovm/tests/files/len8.gno new file mode 100644 index 00000000000..6ca5a6ae8fa --- /dev/null +++ b/gnovm/tests/files/len8.gno @@ -0,0 +1,10 @@ +package main + +func main() { + println(len(struct { + A, B int + }{})) +} + +// Error: +// unexpected type for len(): struct{A int;B int} diff --git a/gnovm/tests/files/map15.gno b/gnovm/tests/files/map15.gno index 99e28fdeeda..1c1775fa30b 100644 --- a/gnovm/tests/files/map15.gno +++ b/gnovm/tests/files/map15.gno @@ -6,8 +6,8 @@ func main() { users := make(map[string]string) v := users["a"] - fmt.Println("v:", v) + fmt.Println("v:", v, ";") } // Output: -// v: +// v: ; diff --git a/gnovm/tests/files/method27.gno b/gnovm/tests/files/method27.gno index cb046d27449..ccb3c53d250 100644 --- a/gnovm/tests/files/method27.gno +++ b/gnovm/tests/files/method27.gno @@ -13,8 +13,8 @@ type AuthenticatedRequest struct { func main() { a := &AuthenticatedRequest{} - fmt.Println("ua:", a.UserAgent()) + fmt.Println("ua:", a.UserAgent(), ";") } // Output: -// ua: +// ua: ; diff --git a/gnovm/tests/files/panic0.gno b/gnovm/tests/files/panic0.gno index 66a38b42c46..06460ca9d07 100644 --- a/gnovm/tests/files/panic0.gno +++ b/gnovm/tests/files/panic0.gno @@ -4,5 +4,10 @@ func main() { panic("wtf") } +// Stacktrace: +// panic: wtf +// main() +// main/files/panic0.gno:4 + // Error: // wtf diff --git a/gnovm/tests/files/panic0a.gno b/gnovm/tests/files/panic0a.gno new file mode 100644 index 00000000000..575bb7cce91 --- /dev/null +++ b/gnovm/tests/files/panic0a.gno @@ -0,0 +1,45 @@ +// Test panic with function call with all kind of expressions +package main + +type S struct { + s string +} + +func f(it1 int, it2, it3 int, pit *int, b bool, strs []string, s S, m map[string]string, t func(s string) string) { + panic("wtf") +} + +func main() { + vit := 1 + lit := []int{1} + var ( + pit *int = &vit + v interface{} + ) + b := true + v = 1 + + f( + v.(int), + lit[0], + *pit, + &vit, + !b, + []string{"a", "b"}, + S{s: "c"}, + map[string]string{"d": "gg", "test": "test"}, + func(s string) string { + return s + }, + ) +} + +// Stacktrace: +// panic: wtf +// f(v.((const-type int)),lit[0],*pit,&vit,!b,[](const-type string),S,map[(const-type string)] (const-type string),func(s (const-type string)) (const-type string){ ... }) +// main/files/panic0a.gno:9 +// main() +// main/files/panic0a.gno:22 + +// Error: +// wtf diff --git a/gnovm/tests/files/panic0b.gno b/gnovm/tests/files/panic0b.gno index bf3b343f785..55a7b21015a 100644 --- a/gnovm/tests/files/panic0b.gno +++ b/gnovm/tests/files/panic0b.gno @@ -14,6 +14,19 @@ func f() { panic("first") } +// Stacktrace: +// panic: first +// f() +// main/files/panic0b.gno:14 +// main() +// main/files/panic0b.gno:4 +// ... 1 panic(s) elided ... +// panic: third +// f() +// main/files/panic0b.gno:9 +// main() +// main/files/panic0b.gno:4 + // Error: // first // second diff --git a/gnovm/tests/files/panic0c.gno b/gnovm/tests/files/panic0c.gno new file mode 100644 index 00000000000..c331ee3bd17 --- /dev/null +++ b/gnovm/tests/files/panic0c.gno @@ -0,0 +1,87 @@ +package main + +type S struct { + s string +} + +func f( + s string, + b bool, + by byte, + it int, + it8 int8, + it16 int16, + it32 int32, + it64 int64, + uit uint, + uit8 uint8, + uit16 uint16, + uit32 uint32, + uit64 uint64, + ft32 float32, + ft64 float64, + strs []string, + st S, + m map[string]string, + t func(s string) string, +) { + panic("wtf") +} + +func main() { + strs := []string{"a", "b"} + st := S{"c"} + m := map[string]string{"d": "gg", "test": "test"} + t := func(s string) string { + return s + } + + const s string = "a" + + const b bool = true + + const by byte = 0 + + const it int = 1 + const it8 int8 = 1 + const it16 int16 = 1 + const it32 int32 = 1 + const it64 int64 = 1 + const uit uint = 1 + const uit8 uint8 = 1 + const uit16 uint16 = 1 + const uit32 uint32 = 1 + const uit64 uint64 = 1 + const ft32 float32 = 1 + const ft64 float64 = 1 + f( + s, + b, + by, + it, + it8, + it16, + it32, + it64, + uit, + uit8, + uit16, + uit32, + uit64, + ft32, + ft64, + strs, + st, + m, + t) +} + +// Stacktrace: +// panic: wtf +// f(a,true,0,1,1,1,1,1,1,1,1,1,1,1,1,strs,st,m,t) +// main/files/panic0c.gno:28 +// main() +// main/files/panic0c.gno:57 + +// Error: +// wtf diff --git a/gnovm/tests/files/panic1.gno b/gnovm/tests/files/panic1.gno index 483d43e53d1..235ba4a0b34 100644 --- a/gnovm/tests/files/panic1.gno +++ b/gnovm/tests/files/panic1.gno @@ -22,5 +22,10 @@ func main() { panic("here") } +// Stacktrace: +// panic: here +// main() +// main/files/panic1.gno:22 + // Error: // here diff --git a/gnovm/tests/files/panic2a.gno b/gnovm/tests/files/panic2a.gno new file mode 100644 index 00000000000..7310d6cce71 --- /dev/null +++ b/gnovm/tests/files/panic2a.gno @@ -0,0 +1,275 @@ +package main + +func p(i int) { + if i == 200 { + panic("here") + } + p(i + 1) +} + +func main() { + p(0) +} + +// Stacktrace: +// panic: here +// p(i + 1) +// main/files/panic2a.gno:5 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// ...74 frame(s) elided... +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(i + 1) +// main/files/panic2a.gno:7 +// p(0) +// main/files/panic2a.gno:7 +// main() +// main/files/panic2a.gno:11 + +// Error: +// here diff --git a/gnovm/tests/files/panic2b.gno b/gnovm/tests/files/panic2b.gno new file mode 100644 index 00000000000..7c356409aad --- /dev/null +++ b/gnovm/tests/files/panic2b.gno @@ -0,0 +1,44 @@ +package main + +func p(i int) { + defer func() { + panic("here") + }() + if i == 4 { + panic("here") + } + p(i + 1) +} + +func main() { + p(0) +} + +// Stacktrace: +// panic: here +// p(i + 1) +// main/files/panic2b.gno:8 +// p(i + 1) +// main/files/panic2b.gno:10 +// p(i + 1) +// main/files/panic2b.gno:10 +// p(i + 1) +// main/files/panic2b.gno:10 +// p(0) +// main/files/panic2b.gno:10 +// main() +// main/files/panic2b.gno:14 +// ... 4 panic(s) elided ... +// panic: here +// p(0) +// main/files/panic2b.gno:5 +// main() +// main/files/panic2b.gno:14 + +// Error: +// here +// here +// here +// here +// here +// here diff --git a/gnovm/tests/files/pointer_eq.gno b/gnovm/tests/files/pointer_eq.gno new file mode 100644 index 00000000000..f144470e492 --- /dev/null +++ b/gnovm/tests/files/pointer_eq.gno @@ -0,0 +1,36 @@ +package main + +type A *int + +func main() { + b := new(int) + var a A + a = b + + var c *int + c = b + + println(areEqual(a, b)) + println(areEqual(b, c)) + println(CheckNil()) +} + +func areEqual(v, w interface{}) bool { + return v == w +} + +type Node struct{} + +func CheckNil() bool { + var n *Node + return n.IsNil() +} + +func (n *Node) IsNil() bool { + return n == nil +} + +// Output: +// false +// true +// true diff --git a/gnovm/tests/files/recover10.gno b/gnovm/tests/files/recover10.gno index 16dff4d4fed..de083a322a4 100644 --- a/gnovm/tests/files/recover10.gno +++ b/gnovm/tests/files/recover10.gno @@ -6,5 +6,10 @@ func main() { panic("ahhhhh") } +// Stacktrace: +// panic: ahhhhh +// main() +// main/files/recover10.gno:6 + // Error: // ahhhhh diff --git a/gnovm/tests/files/recover1b.gno b/gnovm/tests/files/recover1b.gno index 9e3b9ba72b6..978b4988329 100644 --- a/gnovm/tests/files/recover1b.gno +++ b/gnovm/tests/files/recover1b.gno @@ -10,5 +10,10 @@ func main() { panic("test panic") } +// Stacktrace: +// panic: other panic +// main() +// main/files/recover1b.gno:8 + // Error: // other panic diff --git a/gnovm/tests/files/recover8.gno b/gnovm/tests/files/recover8.gno index 53b31f05468..d144a4f986f 100644 --- a/gnovm/tests/files/recover8.gno +++ b/gnovm/tests/files/recover8.gno @@ -20,5 +20,12 @@ func main() { doSomething() } +// Stacktrace: +// panic: do something panic +// doSomething() +// main/files/recover8.gno:7 +// main() +// main/files/recover8.gno:20 + // Error: // do something panic diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno new file mode 100644 index 00000000000..8279e247d84 --- /dev/null +++ b/gnovm/tests/files/recursive1.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// main/files/recursive1.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1a.gno b/gnovm/tests/files/recursive1a.gno new file mode 100644 index 00000000000..87681e1fcdd --- /dev/null +++ b/gnovm/tests/files/recursive1a.gno @@ -0,0 +1,15 @@ +package main + +type S1 *S + +type S struct { + T S1 +} + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true \ No newline at end of file diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno new file mode 100644 index 00000000000..2893baf8fca --- /dev/null +++ b/gnovm/tests/files/recursive1b.gno @@ -0,0 +1,16 @@ +package main + +type S struct { + T *S + B Integer +} + +type Integer int + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno new file mode 100644 index 00000000000..7797f375027 --- /dev/null +++ b/gnovm/tests/files/recursive1c.gno @@ -0,0 +1,17 @@ +package main + +import "fmt" + +type S struct { + A [2][2]S +} + +func main() { + var a, b S + + fmt.Println(a) + fmt.Println(b) +} + +// Error: +// main/files/recursive1c.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno new file mode 100644 index 00000000000..22bf172b5ac --- /dev/null +++ b/gnovm/tests/files/recursive1d.gno @@ -0,0 +1,17 @@ +package main + +import "fmt" + +type S struct { + A [2]S +} + +func main() { + var a, b S + + fmt.Println(a) + fmt.Println(b) +} + +// Error: +// main/files/recursive1d.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1e.gno b/gnovm/tests/files/recursive1e.gno new file mode 100644 index 00000000000..6d1636ba9f3 --- /dev/null +++ b/gnovm/tests/files/recursive1e.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + A [2][]S +} + +func main() { + var a, b S + println(a) +} + +// Output: +// (struct{(array[(nil []main.S),(nil []main.S)] [2][]main.S)} main.S) diff --git a/gnovm/tests/files/recursive1f.gno b/gnovm/tests/files/recursive1f.gno new file mode 100644 index 00000000000..81fe2a5699c --- /dev/null +++ b/gnovm/tests/files/recursive1f.gno @@ -0,0 +1,13 @@ +package main + +func main() { + type S struct { + T S + } + + var a, b S + println(a == b) +} + +// Error: +// main/files/recursive1f.gno:3:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno new file mode 100644 index 00000000000..4ed86f03d58 --- /dev/null +++ b/gnovm/tests/files/recursive2.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X C +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Error: +// main/files/recursive2.gno:1:1: invalid recursive type: A -> B -> C -> A diff --git a/gnovm/tests/files/recursive2a.gno b/gnovm/tests/files/recursive2a.gno new file mode 100644 index 00000000000..9c7dd3e179f --- /dev/null +++ b/gnovm/tests/files/recursive2a.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X int +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive2b.gno b/gnovm/tests/files/recursive2b.gno new file mode 100644 index 00000000000..92d633cdda1 --- /dev/null +++ b/gnovm/tests/files/recursive2b.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X C +} + +type C struct { + X *A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive2c.gno b/gnovm/tests/files/recursive2c.gno new file mode 100644 index 00000000000..3b5c27ed8ea --- /dev/null +++ b/gnovm/tests/files/recursive2c.gno @@ -0,0 +1,21 @@ +package main + +func main() { + type A struct { + X B + } + + type B struct { + X C + } + + type C struct { + X A + } + + var p, q A + println(p == q) +} + +// Error: +// main/files/recursive2c.gno:3:1: name B not defined in fileset with files [files/recursive2c.gno] diff --git a/gnovm/tests/files/recursive2d.gno b/gnovm/tests/files/recursive2d.gno new file mode 100644 index 00000000000..b2439ba6259 --- /dev/null +++ b/gnovm/tests/files/recursive2d.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X *B +} + +type B struct { + X int +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive3.gno b/gnovm/tests/files/recursive3.gno new file mode 100644 index 00000000000..552c086c91b --- /dev/null +++ b/gnovm/tests/files/recursive3.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T *S +} + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive4.gno b/gnovm/tests/files/recursive4.gno new file mode 100644 index 00000000000..29392cb35ab --- /dev/null +++ b/gnovm/tests/files/recursive4.gno @@ -0,0 +1,15 @@ +package main + +import "time" + +type Duration struct { + t time.Duration +} + +func main() { + var a, b Duration + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive4a.gno b/gnovm/tests/files/recursive4a.gno new file mode 100644 index 00000000000..8b4d13b4785 --- /dev/null +++ b/gnovm/tests/files/recursive4a.gno @@ -0,0 +1,9 @@ +package main + +type time time.Duration + +func main() { +} + +// Error: +// main/files/recursive4a.gno:1:1: invalid recursive type: time -> time diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno new file mode 100644 index 00000000000..1c2fbd89fb8 --- /dev/null +++ b/gnovm/tests/files/recursive5.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// main/files/recursive5.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive6.gno b/gnovm/tests/files/recursive6.gno new file mode 100644 index 00000000000..73858b2ea1b --- /dev/null +++ b/gnovm/tests/files/recursive6.gno @@ -0,0 +1,23 @@ +package main + +type SelfReferencing interface { + Self() SelfReferencing +} + +type Implementation struct { + // Some implementation details... +} + +func (impl Implementation) Self() SelfReferencing { + return &impl +} + +func main() { + var obj Implementation + var intf SelfReferencing = obj + _ = intf.Self() + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/recursive6a.gno b/gnovm/tests/files/recursive6a.gno new file mode 100644 index 00000000000..8123fc626a5 --- /dev/null +++ b/gnovm/tests/files/recursive6a.gno @@ -0,0 +1,12 @@ +package main + +type SelfReferencing interface { + SelfReferencing +} + +func main() { + println("ok") +} + +// Error: +// main/files/recursive6a.gno:1:1: invalid recursive type: SelfReferencing -> SelfReferencing diff --git a/gnovm/tests/files/recursive7.gno b/gnovm/tests/files/recursive7.gno new file mode 100644 index 00000000000..9bd8a56995d --- /dev/null +++ b/gnovm/tests/files/recursive7.gno @@ -0,0 +1,10 @@ +package main + +type S []S + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/recursive7a.gno b/gnovm/tests/files/recursive7a.gno new file mode 100644 index 00000000000..b3c57516f13 --- /dev/null +++ b/gnovm/tests/files/recursive7a.gno @@ -0,0 +1,8 @@ +package main + +type S [2]S + +func main() {} + +// Error: +// main/files/recursive7a.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive8.gno b/gnovm/tests/files/recursive8.gno new file mode 100644 index 00000000000..1f9325ae35c --- /dev/null +++ b/gnovm/tests/files/recursive8.gno @@ -0,0 +1,8 @@ +package main + +type Int Int + +func main() {} + +// Error: +// main/files/recursive8.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9.gno b/gnovm/tests/files/recursive9.gno new file mode 100644 index 00000000000..8181be55d33 --- /dev/null +++ b/gnovm/tests/files/recursive9.gno @@ -0,0 +1,8 @@ +package main + +type Int = Int + +func main() {} + +// Error: +// main/files/recursive9.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9a.gno b/gnovm/tests/files/recursive9a.gno new file mode 100644 index 00000000000..b96efa090e4 --- /dev/null +++ b/gnovm/tests/files/recursive9a.gno @@ -0,0 +1,8 @@ +package main + +type Int = *Int + +func main() {} + +// Error: +// main/files/recursive9a.gno:1:1: invalid recursive type: Int -> Int \ No newline at end of file diff --git a/gnovm/tests/files/recursive9b.gno b/gnovm/tests/files/recursive9b.gno new file mode 100644 index 00000000000..e033349d597 --- /dev/null +++ b/gnovm/tests/files/recursive9b.gno @@ -0,0 +1,8 @@ +package main + +type Int = func() Int + +func main() {} + +// Error: +// main/files/recursive9b.gno:1:1: invalid recursive type: Int -> Int \ No newline at end of file diff --git a/gnovm/tests/files/recursive9c.gno b/gnovm/tests/files/recursive9c.gno new file mode 100644 index 00000000000..ad865978920 --- /dev/null +++ b/gnovm/tests/files/recursive9c.gno @@ -0,0 +1,8 @@ +package main + +type Int = []Int + +func main() {} + +// Error: +// main/files/recursive9c.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9d.gno b/gnovm/tests/files/recursive9d.gno new file mode 100644 index 00000000000..ae7310ede0f --- /dev/null +++ b/gnovm/tests/files/recursive9d.gno @@ -0,0 +1,10 @@ +package main + +type S = struct { + *S +} + +func main() {} + +// Error: +// main/files/recursive9d.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/std5_stdlibs.gno b/gnovm/tests/files/std5_stdlibs.gno index d8de58518f1..4afa09da8d3 100644 --- a/gnovm/tests/files/std5_stdlibs.gno +++ b/gnovm/tests/files/std5_stdlibs.gno @@ -11,5 +11,14 @@ func main() { println(caller2) } +// Stacktrace: +// panic: frame not found +// callerAt(n) +// gonative:std.callerAt +// std.GetCallerAt(2) +// std/native.gno:44 +// main() +// main/files/std5_stdlibs.gno:10 + // Error: // frame not found diff --git a/gnovm/tests/files/std8_stdlibs.gno b/gnovm/tests/files/std8_stdlibs.gno index 6964cec1d3d..ab5e15bd618 100644 --- a/gnovm/tests/files/std8_stdlibs.gno +++ b/gnovm/tests/files/std8_stdlibs.gno @@ -21,5 +21,18 @@ func main() { testutils.WrapCall(inner) } +// Stacktrace: +// panic: frame not found +// callerAt(n) +// gonative:std.callerAt +// std.GetCallerAt(4) +// std/native.gno:44 +// fn() +// main/files/std8_stdlibs.gno:16 +// testutils.WrapCall(inner) +// gno.land/p/demo/testutils/misc.gno:5 +// main() +// main/files/std8_stdlibs.gno:21 + // Error: // frame not found diff --git a/gnovm/tests/files/time17_native.gno b/gnovm/tests/files/time17_native.gno new file mode 100644 index 00000000000..6733c1381cb --- /dev/null +++ b/gnovm/tests/files/time17_native.gno @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + now := time.Now() + now.In(nil) +} + +// Error: +// time: missing Location in call to Time.In + +// Stacktrace: +// now.In(gonative{*time.Location}) +// gofunction:func(*time.Location) time.Time +// main() +// main/files/time17_native.gno:10 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/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 index 041034e4bd0..f6609a3d18c 100644 --- a/gnovm/tests/files/typeassert1.gno +++ b/gnovm/tests/files/typeassert1.gno @@ -9,5 +9,10 @@ func main() { _ = a.(A) } +// Stacktrace: +// panic: interface conversion: interface is nil, not main.A +// main() +// main/files/typeassert1.gno:9 + // Error: // interface conversion: interface is nil, not main.A diff --git a/gnovm/tests/files/typeassert2a.gno b/gnovm/tests/files/typeassert2a.gno index 0441bf83437..bfbd24d38bd 100644 --- a/gnovm/tests/files/typeassert2a.gno +++ b/gnovm/tests/files/typeassert2a.gno @@ -11,5 +11,10 @@ func main() { }() } +// Stacktrace: +// panic: interface conversion: interface is nil, not main.A +// main() +// main/files/typeassert2a.gno:10 + // Error: // interface conversion: interface is nil, not main.A diff --git a/gnovm/tests/files/typeassert4a.gno b/gnovm/tests/files/typeassert4a.gno index 46d3728de1f..09e9ae06aa1 100644 --- a/gnovm/tests/files/typeassert4a.gno +++ b/gnovm/tests/files/typeassert4a.gno @@ -58,5 +58,5 @@ func main() { // interface conversion: interface is nil, not main.Setter // interface conversion: interface is nil, not main.Setter // ok -// main.ValueSetter doesn't implement interface{Set func(string)()} +// main.ValueSetter doesn't implement interface{Set func(string)()} (method Set has pointer receiver) // ok diff --git a/gnovm/tests/files/typeassert6a.gno b/gnovm/tests/files/typeassert6a.gno index f4b925cfeee..e55f077c334 100644 --- a/gnovm/tests/files/typeassert6a.gno +++ b/gnovm/tests/files/typeassert6a.gno @@ -36,5 +36,5 @@ func main() { } // Output: -// int doesn't implement interface{Do func(string)()} -// *int doesn't implement interface{Do func(string)()} +// 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/typeassert9.gno b/gnovm/tests/files/typeassert9.gno index d9d5bad55af..6ea072661c1 100644 --- a/gnovm/tests/files/typeassert9.gno +++ b/gnovm/tests/files/typeassert9.gno @@ -16,5 +16,10 @@ func main() { _ = reader.(Writer) } +// Stacktrace: +// panic: interface conversion: interface is nil, not main.Writer +// main() +// main/files/typeassert9.gno:16 + // 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/assign_type_assertion_c.gno b/gnovm/tests/files/types/assign_type_assertion_c.gno index b274497357a..d1c1cadcb91 100644 --- a/gnovm/tests/files/types/assign_type_assertion_c.gno +++ b/gnovm/tests/files/types/assign_type_assertion_c.gno @@ -30,4 +30,4 @@ func main() { } // Error: -// main/files/types/assign_type_assertion_c.gno:23:2: interface{IsSet func()(bool)} does not implement interface{IsNotSet func()(bool)} +// main/files/types/assign_type_assertion_c.gno:23:2: interface{IsSet func()(bool)} does not implement interface{IsNotSet func()(bool)} (missing method IsNotSet) diff --git a/gnovm/tests/files/types/cmp_databyte.gno b/gnovm/tests/files/types/cmp_databyte.gno new file mode 100644 index 00000000000..0583ed9a259 --- /dev/null +++ b/gnovm/tests/files/types/cmp_databyte.gno @@ -0,0 +1,12 @@ +package main + +import "bytes" + +func main() { + cmp := bytes.Compare([]byte("hello"), []byte("hey")) + println(cmp) + +} + +// Output: +// -1 diff --git a/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno new file mode 100644 index 00000000000..fb4ac682243 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_1.gno b/gnovm/tests/files/types/cmp_iface_1.gno new file mode 100644 index 00000000000..551b4acf0f1 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_1.gno @@ -0,0 +1,29 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// typed +var errCmp error = errors.New("XXXX") + +// special case: +// one is interface +func main() { + const e Error = Error(0) // typed const + if e == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_2.gno b/gnovm/tests/files/types/cmp_iface_2.gno new file mode 100644 index 00000000000..f3bf14b2b7b --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_2.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/cmp_iface_3_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno new file mode 100644 index 00000000000..9c4cb0e5ea0 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(1) == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_4.gno b/gnovm/tests/files/types/cmp_iface_4.gno new file mode 100644 index 00000000000..a4ae0463291 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_4.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") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno new file mode 100644 index 00000000000..e706c74808e --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if errCmp == int64(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/cmp_iface_5_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/cmp_iface_6.gno b/gnovm/tests/files/types/cmp_iface_6.gno new file mode 100644 index 00000000000..6abc84992ea --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_6.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 int64 + +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) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/cmp_iface_7.gno b/gnovm/tests/files/types/cmp_iface_7.gno new file mode 100644 index 00000000000..a0ba3e8a0d3 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_7.gno @@ -0,0 +1,24 @@ +package main + +import "fmt" + +func check(v1, v2 interface{}) bool { + return v1 == v2 +} + +func main() { + type t1 int + type t2 int + v1 := t1(1) + v2 := t2(1) + v3 := t2(3) + + fmt.Println("v1, v2", v1, v2, check(v1, v2)) + fmt.Println("v1, v3", v1, v3, check(v1, v3)) + fmt.Println("v2, v3", v2, v3, check(v2, v3)) +} + +// Output: +// v1, v2 1 1 false +// v1, v3 1 3 false +// v2, v3 1 3 false diff --git a/gnovm/tests/files/types/cmp_primitive_0.gno b/gnovm/tests/files/types/cmp_primitive_0.gno new file mode 100644 index 00000000000..2c968b5158f --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_0.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/cmp_primitive_1.gno b/gnovm/tests/files/types/cmp_primitive_1.gno new file mode 100644 index 00000000000..2f2e1c94ef1 --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_1.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/cmp_primitive_1.gno:14:5: cannot use untyped Bigint as StringKind diff --git a/gnovm/tests/files/types/cmp_primitive_2.gno b/gnovm/tests/files/types/cmp_primitive_2.gno new file mode 100644 index 00000000000..34c8a24cba2 --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_2.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/cmp_primitive_3.gno b/gnovm/tests/files/types/cmp_primitive_3.gno new file mode 100644 index 00000000000..c1692c8019c --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_3.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 typed const, right is untyped const +func main() { + if Error(1) == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/cmp_slice_0.gno b/gnovm/tests/files/types/cmp_slice_0.gno new file mode 100644 index 00000000000..1db537a4d8c --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_0.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/cmp_slice_1.gno b/gnovm/tests/files/types/cmp_slice_1.gno new file mode 100644 index 00000000000..76f2db8d7d8 --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_1.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/cmp_slice_2.gno b/gnovm/tests/files/types/cmp_slice_2.gno new file mode 100644 index 00000000000..018d53fa81a --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_2.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/cmp_slice_3.gno b/gnovm/tests/files/types/cmp_slice_3.gno new file mode 100644 index 00000000000..2795d618e91 --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_3.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/cmp_slice_4.gno b/gnovm/tests/files/types/cmp_slice_4.gno new file mode 100644 index 00000000000..2bdd3191eff --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := 1 + println(a == expected) // both typed +} + +// Error: +// main/files/types/cmp_slice_4.gno:6:10: cannot use int as string diff --git a/gnovm/tests/files/types/cmp_typeswitch.gno b/gnovm/tests/files/types/cmp_typeswitch.gno new file mode 100644 index 00000000000..721dfc9579a --- /dev/null +++ b/gnovm/tests/files/types/cmp_typeswitch.gno @@ -0,0 +1,18 @@ +package main + +func main() { + var l interface{} + l = int64(0) + + switch val := l.(type) { + case int64, int: + if val == 0 { + println("l is zero") + } else { + println("NOT zero") + } + } +} + +// Output: +// NOT zero diff --git a/gnovm/tests/files/types/cmp_typeswitch_a.gno b/gnovm/tests/files/types/cmp_typeswitch_a.gno new file mode 100644 index 00000000000..f70fcb3d3d6 --- /dev/null +++ b/gnovm/tests/files/types/cmp_typeswitch_a.gno @@ -0,0 +1,18 @@ +package main + +func main() { + var l interface{} + l = int(0) + + switch val := l.(type) { + case int64, int: + if val == 0 { + println("l is zero") + } else { + println("NOT zero") + } + } +} + +// Output: +// l is zero diff --git a/gnovm/tests/files/types/eql_0b4_stdlibs.gno b/gnovm/tests/files/types/eql_0b4_stdlibs.gno index 70f5bf31296..eac923c6d31 100644 --- a/gnovm/tests/files/types/eql_0b4_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0b4_stdlibs.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/types/eql_0b4_stdlibs.gno:9:10: bigint does not implement .uverse.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_0f0_stdlibs.gno b/gnovm/tests/files/types/eql_0f0_stdlibs.gno index bbd49d6cf93..4947627cba4 100644 --- a/gnovm/tests/files/types/eql_0f0_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f0_stdlibs.gno @@ -25,4 +25,4 @@ func main() { } // Error: -// main/files/types/eql_0f0_stdlibs.gno:19:5: bigint does not implement .uverse.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_0f1_stdlibs.gno b/gnovm/tests/files/types/eql_0f1_stdlibs.gno index f3feffa7ec6..cab7fcfab33 100644 --- a/gnovm/tests/files/types/eql_0f1_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f1_stdlibs.gno @@ -25,4 +25,4 @@ func main() { } // Error: -// main/files/types/eql_0f1_stdlibs.gno:19:5: int64 does not implement .uverse.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_0f2d.gno b/gnovm/tests/files/types/eql_0f2d.gno index 5ad121f515b..f3bf14b2b7b 100644 --- a/gnovm/tests/files/types/eql_0f2d.gno +++ b/gnovm/tests/files/types/eql_0f2d.gno @@ -19,7 +19,7 @@ func (e Error) Error() string { func main() { var e0 E e0 = Error(0) - fmt.Printf("%T \n", e0) + fmt.Printf("%T\n", e0) if e0 == Error(0) { println("what the firetruck?") } else { diff --git a/gnovm/tests/files/types/eql_0f2e.gno b/gnovm/tests/files/types/eql_0f2e.gno index ea03028f5e1..c9c24dcd4eb 100644 --- a/gnovm/tests/files/types/eql_0f2e.gno +++ b/gnovm/tests/files/types/eql_0f2e.gno @@ -19,7 +19,7 @@ func (e Error) Error() string { func main() { var e0 E e0 = Error(0) - fmt.Printf("%T \n", e0) + fmt.Printf("%T\n", e0) if Error(0) == e0 { println("what the firetruck?") } else { diff --git a/gnovm/tests/files/types/eql_0f41_stdlibs.gno b/gnovm/tests/files/types/eql_0f41_stdlibs.gno index f4d35a2af4b..be78ea6ed79 100644 --- a/gnovm/tests/files/types/eql_0f41_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f41_stdlibs.gno @@ -32,4 +32,4 @@ func main() { } // Error: -// main/files/types/eql_0f41_stdlibs.gno:27:5: main.animal does not implement .uverse.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_0f49.gno b/gnovm/tests/files/types/eql_0f49.gno new file mode 100644 index 00000000000..b5a4bf4ed05 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f49.gno @@ -0,0 +1,21 @@ +package main + +func main() { + a := "1234" + b := "1234" + + cond := a == b + println(cond) + println(cond == (a == b)) + println((a == b) == cond) + println((a == b) == (a == b)) + println(cond && (a == b)) + println(cond || (a > b)) + +} + +// true +// true +// true +// true +// true diff --git a/gnovm/tests/files/types/eql_0f8_stdlibs.gno b/gnovm/tests/files/types/eql_0f8_stdlibs.gno index 1ab191c59c4..a6e24110432 100644 --- a/gnovm/tests/files/types/eql_0f8_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f8_stdlibs.gno @@ -24,4 +24,4 @@ func main() { } // Error: -// main/files/types/eql_0f8_stdlibs.gno:19:5: int64 does not implement .uverse.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/iface_eql.gno b/gnovm/tests/files/types/iface_eql.gno new file mode 100644 index 00000000000..97daa27c184 --- /dev/null +++ b/gnovm/tests/files/types/iface_eql.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var l interface{} = 1 + println(int8(1) == l) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/runtime_a2.gno b/gnovm/tests/files/types/runtime_a2.gno index d53c9c4d970..0c1b8453591 100644 --- a/gnovm/tests/files/types/runtime_a2.gno +++ b/gnovm/tests/files/types/runtime_a2.gno @@ -8,7 +8,7 @@ func gen() interface{} { func main() { r := gen() - fmt.Printf("%T \n", r) + fmt.Printf("%T\n", r) } // Output: diff --git a/gnovm/tests/files/types/shift_a12.gno b/gnovm/tests/files/types/shift_a12.gno index 5735854d684..272373b19a6 100644 --- a/gnovm/tests/files/types/shift_a12.gno +++ b/gnovm/tests/files/types/shift_a12.gno @@ -6,7 +6,7 @@ func main() { a := uint(1) r := uint64(1) << a println(r) - fmt.Printf("%T \n", r) + fmt.Printf("%T\n", r) } // Output: diff --git a/gnovm/tests/files/types/shift_a13.gno b/gnovm/tests/files/types/shift_a13.gno index 7d70cc3589a..4ff5c6b7753 100644 --- a/gnovm/tests/files/types/shift_a13.gno +++ b/gnovm/tests/files/types/shift_a13.gno @@ -4,7 +4,7 @@ import "fmt" func main() { var r uint64 = 1 << int8(1) - fmt.Printf("%T \n", r) + fmt.Printf("%T\n", r) println(r) } diff --git a/gnovm/tests/files/var18.gno b/gnovm/tests/files/var18.gno index 771ddcdffb5..f01d3871d39 100644 --- a/gnovm/tests/files/var18.gno +++ b/gnovm/tests/files/var18.gno @@ -1,8 +1,8 @@ package main func main() { - a, b, c := 1, 2 + var a, b, c = 1, 2 } // Error: -// main/files/var18.gno:4:2: assignment mismatch: 3 variables but 2 values +// main/files/var18.gno:4:6: missing init expr for c diff --git a/gnovm/tests/files/var19.gno b/gnovm/tests/files/var19.gno new file mode 100644 index 00000000000..99d7452f603 --- /dev/null +++ b/gnovm/tests/files/var19.gno @@ -0,0 +1,12 @@ +package main + +func main() { + var a, b, c = 1, a+1 + + println(a) + println(b) + println(c) +} + +// Error: +// main/files/var19.gno:4:6: missing init expr for c diff --git a/gnovm/tests/files/var20.gno b/gnovm/tests/files/var20.gno new file mode 100644 index 00000000000..6e15fcca6c5 --- /dev/null +++ b/gnovm/tests/files/var20.gno @@ -0,0 +1,12 @@ +package main + +func r() int { + return 1 +} + +func main() { + var a, b, c = r() +} + +// Error: +// main/files/var20.gno:8:6: assignment mismatch: 3 variable(s) but r() returns 1 value(s) diff --git a/gnovm/tests/files/var21.gno b/gnovm/tests/files/var21.gno new file mode 100644 index 00000000000..b593984aa87 --- /dev/null +++ b/gnovm/tests/files/var21.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b = 2, foo() + + println(a, b) +} + +// Error: +// main/files/var21.gno:8:6: multiple-value foo (value of type [int bool]) in single-value context diff --git a/gnovm/tests/files/var22.gno b/gnovm/tests/files/var22.gno new file mode 100644 index 00000000000..3f85f0f156d --- /dev/null +++ b/gnovm/tests/files/var22.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b, c = 2, foo() + + println(a, b, c) +} + +// Error: +// main/files/var22.gno:8:6: missing init expr for c diff --git a/gnovm/tests/files/var22b.gno b/gnovm/tests/files/var22b.gno new file mode 100644 index 00000000000..22a52fcc811 --- /dev/null +++ b/gnovm/tests/files/var22b.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b, c = 2, 3, 4, foo() + + println(a, b, c) +} + +// Error: +// main/files/var22b.gno:8:6: extra init expr foo() diff --git a/gnovm/tests/files/var23.gno b/gnovm/tests/files/var23.gno new file mode 100644 index 00000000000..b9f98311411 --- /dev/null +++ b/gnovm/tests/files/var23.gno @@ -0,0 +1,9 @@ +package main + +var a, b, c = 1, 2 + +func main() { +} + +// Error: +// main/files/var23.gno:3:5: assignment mismatch: 3 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/var24.gno b/gnovm/tests/files/var24.gno new file mode 100644 index 00000000000..ddfa82a1e4e --- /dev/null +++ b/gnovm/tests/files/var24.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var i interface{} = 1 + var a, b, c = i.(int) +} + +// Error: +// main/files/var24.gno:5:6: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/var25.gno b/gnovm/tests/files/var25.gno new file mode 100644 index 00000000000..999979dcf71 --- /dev/null +++ b/gnovm/tests/files/var25.gno @@ -0,0 +1,9 @@ +package main + +func main() { + s := []string{"1", "2"} + var a, b, c = s[0] +} + +// Error: +// main/files/var25.gno:5:6: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/var26.gno b/gnovm/tests/files/var26.gno new file mode 100644 index 00000000000..71b6f3a4eb0 --- /dev/null +++ b/gnovm/tests/files/var26.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + var a, c = 1, 2, 3 + fmt.Println(a, c) +} + +// Error: +// main/files/var26.gno:6:6: extra init expr 3 diff --git a/gnovm/tests/files/var29.gno b/gnovm/tests/files/var29.gno new file mode 100644 index 00000000000..a37ccddd240 --- /dev/null +++ b/gnovm/tests/files/var29.gno @@ -0,0 +1,14 @@ +package main + +func main() { + println(a) + println(b) + println(c) +} + +var a, b, c = 1, a + 1, b + 1 + +// Output: +// 1 +// 2 +// 3 \ No newline at end of file diff --git a/gnovm/tests/files/var30.gno b/gnovm/tests/files/var30.gno new file mode 100644 index 00000000000..c8c9efabdef --- /dev/null +++ b/gnovm/tests/files/var30.gno @@ -0,0 +1,17 @@ +package main + +func main() { + println(a) + println(b) + println(c) + println(d) +} + +var a, b, c = 1, a + d, 3 +var d = a + +// Output: +// 1 +// 2 +// 3 +// 1 \ No newline at end of file diff --git a/gnovm/tests/files/vardecl.gno b/gnovm/tests/files/vardecl.gno new file mode 100644 index 00000000000..34390f26a6a --- /dev/null +++ b/gnovm/tests/files/vardecl.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var i interface{} = 1 + var a, ok = i.(int) + println(a, ok) + + var b, c = doSomething() + println(b, c) + + m := map[string]int{"a": 1, "b": 2} + var d, okk = m["d"] + println(d, okk) +} + +func doSomething() (int, string) { + return 4, "hi" +} + +// Output: +// 1 true +// 4 hi +// 0 false diff --git a/gnovm/tests/files/zrealm_panic.gno b/gnovm/tests/files/zrealm_panic.gno new file mode 100644 index 00000000000..3864e2a7f7f --- /dev/null +++ b/gnovm/tests/files/zrealm_panic.gno @@ -0,0 +1,20 @@ +// PKGPATH: gno.land/r/test +package test + +type MyStruct struct{} + +func (ms MyStruct) Panic() { + panic("panic") +} + +func main() { + ms := MyStruct{} + ms.Panic() +} + +// Stacktrace: +// panic: panic +// ms.Panic() +// gno.land/r/test/main.gno:7 +// main() +// gno.land/r/test/main.gno:12 diff --git a/gnovm/tests/files/zrealm_tests0_stdlibs.gno b/gnovm/tests/files/zrealm_tests0_stdlibs.gno index f1bb6148eaa..34f8ebc106d 100644 --- a/gnovm/tests/files/zrealm_tests0_stdlibs.gno +++ b/gnovm/tests/files/zrealm_tests0_stdlibs.gno @@ -719,7 +719,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "11", +// "Line": "12", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -765,7 +765,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "15", +// "Line": "16", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -821,7 +821,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "19", +// "Line": "20", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -887,7 +887,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "25", +// "Line": "26", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -933,7 +933,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "29", +// "Line": "30", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -979,7 +979,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "33", +// "Line": "34", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1025,7 +1025,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "37", +// "Line": "38", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1071,7 +1071,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "41", +// "Line": "42", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1150,7 +1150,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "58", +// "Line": "57", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1229,7 +1229,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "54", +// "Line": "53", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1346,7 +1346,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "76", +// "Line": "75", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1382,7 +1382,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "81", +// "Line": "80", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1418,7 +1418,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "89", +// "Line": "88", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1464,7 +1464,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "93", +// "Line": "92", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1520,7 +1520,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "97", +// "Line": "96", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1577,7 +1577,7 @@ func main() { // "Location": { // "Column": "1", // "File": "tests.gno", -// "Line": "101", +// "Line": "100", // "PkgPath": "gno.land/r/demo/tests" // } // }, @@ -1598,6 +1598,174 @@ func main() { // "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": "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.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": "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.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" +// } +// } +// ] +// } +// } // } // ] // } diff --git a/go.mod b/go.mod index c155a23f16b..d890ab020a4 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.22 toolchain go1.22.4 require ( - dario.cat/mergo v1.0.0 - github.com/btcsuite/btcd/btcec/v2 v2.3.3 - github.com/btcsuite/btcd/btcutil v1.1.5 + dario.cat/mergo v1.0.1 + github.com/btcsuite/btcd/btcec/v2 v2.3.4 + github.com/btcsuite/btcd/btcutil v1.1.6 github.com/cockroachdb/apd/v3 v3.2.1 github.com/cosmos/ledger-cosmos-go v0.13.3 github.com/davecgh/go-spew v1.1.1 @@ -18,35 +18,35 @@ require ( github.com/golang/protobuf v1.5.4 github.com/google/gofuzz v1.2.0 github.com/gorilla/mux v1.8.1 - github.com/gorilla/websocket v1.5.1 + github.com/gorilla/websocket v1.5.3 github.com/gotuna/gotuna v0.6.0 github.com/libp2p/go-buffer-pool v0.1.0 - github.com/mattn/go-runewidth v0.0.15 + github.com/mattn/go-runewidth v0.0.16 github.com/pelletier/go-toml v1.9.5 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pmezard/go-difflib v1.0.0 github.com/rogpeppe/go-internal v1.12.0 - github.com/rs/cors v1.11.0 - github.com/rs/xid v1.5.0 + github.com/rs/cors v1.11.1 + github.com/rs/xid v1.6.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.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.etcd.io/bbolt v1.3.11 + go.opentelemetry.io/otel v1.29.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 + go.opentelemetry.io/otel/metric v1.29.0 + go.opentelemetry.io/otel/sdk v1.29.0 + go.opentelemetry.io/otel/sdk/metric v1.29.0 go.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.25.0 + golang.org/x/crypto v0.26.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 + golang.org/x/mod v0.20.0 + golang.org/x/net v0.28.0 + golang.org/x/sync v0.8.0 + golang.org/x/term v0.23.0 + golang.org/x/tools v0.24.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 ) @@ -60,18 +60,18 @@ require ( 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.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.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.28.0 // indirect + go.opentelemetry.io/otel/trace v1.29.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 + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect ) diff --git a/go.sum b/go.sum index 9fde89e8cec..9495dd5b451 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,20 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/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.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= 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.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= -github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/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.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= 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.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -89,12 +91,12 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.0/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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/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= @@ -110,8 +112,9 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= @@ -138,12 +141,17 @@ 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.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/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/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.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= @@ -153,22 +161,22 @@ 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.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -183,14 +191,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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/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.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/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= @@ -200,14 +208,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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.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= @@ -224,36 +232,36 @@ 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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.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.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/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/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/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 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/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/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/misc/deployments/staging.gno.land/docker-compose.yml b/misc/deployments/staging.gno.land/docker-compose.yml index 3479067372d..7d264a34dbd 100644 --- a/misc/deployments/staging.gno.land/docker-compose.yml +++ b/misc/deployments/staging.gno.land/docker-compose.yml @@ -1,130 +1,126 @@ -version: "2" - +name: "staging-gno-land" services: + traefik: + image: "traefik:v2.11" + restart: unless-stopped + command: + - "--api.insecure=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.rpc.address=:26657" + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + - "--entrypoints.web.http.redirections.entrypoint.permanent=true" + - "--entryPoints.web.forwardedHeaders.insecure" + - "--entrypoints.traefik.address=:8080" + - "--entrypoints.websecure.address=:443" + + - "--certificatesresolvers.le.acme.tlschallenge=true" + - "--certificatesresolvers.le.acme.email=dev@gno.land" + - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json" + ports: + - "80:80" + - "443:443" + - "26657:26657" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - ./letsencrypt:/letsencrypt + gnoland: - container_name: gnoland - build: ../../.. + image: ghcr.io/gnolang/gno/gnoland:master + restart: unless-stopped + entrypoint: /entrypoint.sh + working_dir: /gnoroot environment: - - VIRTUAL_HOST=rpc.staging.gno.land - - VIRTUAL_PORT=26657 - - LETSENCRYPT_HOST=rpc.staging.gno.land - - LOG_LEVEL=4 - working_dir: /opt/gno/src/gno.land - command: - - gnoland - - start - - --skip-failing-genesis-txs - - --chainid=staging - - --genesis-remote=staging.gno.land:26657 + CHAIN_ID: staging + MONIKER: gno-staging volumes: - - "./data/gnoland:/opt/gno/src/gno.land/gnoland-data" - ports: - - 26656:26656 - - 26657:26657 - restart: on-failure - logging: - driver: "json-file" - options: - max-file: "10" - max-size: "100m" + - ./gnoland.entrypoint.sh:/entrypoint.sh + #ports: + # - 26656:26656 + labels: + com.centurylinklabs.watchtower.enable: "true" + traefik.enable: "true" + traefik.http.routers.gnoland.entrypoints: "web,websecure" + traefik.http.routers.gnoland.rule: "Host(`rpc.staging.gno.land`)" + traefik.http.routers.gnoland.service: gnoland-rpc + traefik.http.routers.gnoland.tls: "true" + traefik.http.routers.gnoland.tls.certresolver: "le" + traefik.http.routers.gnoland-rpc.entrypoints: "rpc" + traefik.http.routers.gnoland-rpc.rule: "PathPrefix(`/`)" + traefik.http.routers.gnoland-rpc.service: gnoland-rpc + traefik.http.services.gnoland-rpc.loadbalancer.server.port: 26657 gnoweb: - container_name: gnoweb - build: ../../.. - command: + image: ghcr.io/gnolang/gno/gnoweb:master + restart: unless-stopped + env_file: ".env" + entrypoint: - gnoweb - - --bind=0.0.0.0:80 - - --remote=gnoland:26657 - - --captcha-site=$RECAPTCHA_SITE_KEY - - --faucet-url=https://faucet-staging.gno.land/ - - --help-chainid=staging - - --help-remote=staging.gno.land:26657 + - --bind=0.0.0.0:8888 + - --remote=http://traefik:26657 + - --faucet-url=https://faucet-api.staging.gno.land + - --captcha-site=$CAPTCHA_SITE_KEY - --with-analytics - volumes: - - "./overlay:/overlay:ro" - links: - - gnoland - environment: - - VIRTUAL_HOST=staging.gno.land - - LETSENCRYPT_HOST=staging.gno.land - # from .env - - RECAPTCHA_SITE_KEY - restart: on-failure - logging: - driver: "json-file" - options: - max-file: "10" - max-size: "100m" + - --help-chainid=staging + - --help-remote=https://rpc.staging.gno.land:443 + labels: + com.centurylinklabs.watchtower.enable: "true" + traefik.enable: "true" + traefik.http.routers.gnoweb.entrypoints: "web,websecure" + traefik.http.routers.gnoweb.rule: "Host(`staging.gno.land`)" + traefik.http.routers.gnoweb.tls: "true" + traefik.http.routers.gnoweb.tls.certresolver: "le" gnofaucet: - container_name: gnofaucet - build: ../../.. - command: sh -xc " - date && - 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:26657 --chain-id staging --home /.gno/ faucet; expect \\\"Enter password\\\"; send \\\"$GNOKEY_PASS\\r\\\"; expect eof\"; - sleep 5; - done - " - links: - - gnoland - environment: - - VIRTUAL_HOST=faucet-staging.gno.land - - VIRTUAL_PORT=5050 - - LETSENCRYPT_HOST=faucet-staging.gno.land - # from .env - - RECAPTCHA_SECRET_KEY - - FAUCET_WORDS - - GNOKEY_PASS - ports: - - 5050 - restart: on-failure - logging: - driver: "json-file" - options: - max-file: "10" - max-size: "100m" + image: ghcr.io/gnolang/gno/gnofaucet-slim + restart: unless-stopped + command: + - "serve" + - "--listen-address=0.0.0.0:5050" + - "--chain-id=staging" + - "--is-behind-proxy=true" + - "--mnemonic=${FAUCET_MNEMONIC}" + - "--num-accounts=1" + - "--remote=http://traefik:26657" + - "--captcha-secret=${CAPTCHA_SECRET_KEY}" + env_file: ".env" + # environment: + # from .env + # - RECAPTCHA_SECRET_KEY + labels: + com.centurylinklabs.watchtower.enable: "true" + traefik.enable: "true" + traefik.http.routers.gnofaucet-api.entrypoints: "websecure" + traefik.http.routers.gnofaucet-api.rule: "Host(`faucet-api.staging.gno.land`) || Host(`faucet-api.staging.gnoteam.com`)" + traefik.http.routers.gnofaucet-api.tls: "true" + traefik.http.routers.gnofaucet-api.tls.certresolver: "le" + traefik.http.middlewares.gnofaucet-ratelimit.ratelimit.average: "6" + traefik.http.middlewares.gnofaucet-ratelimit.ratelimit.period: "1m" - nginx-proxy: - image: nginxproxy/nginx-proxy - container_name: nginx-proxy - ports: - - "80:80" - - "443:443" + watchtower: + image: containrrr/watchtower + restart: unless-stopped + command: --interval 30 --http-api-metrics --label-enable volumes: - - conf:/etc/nginx/conf.d - - vhost:/etc/nginx/vhost.d - - html:/usr/share/nginx/html - - certs:/etc/nginx/certs:ro - - /var/run/docker.sock:/tmp/docker.sock:ro - logging: - driver: "json-file" - options: - max-file: "10" - max-size: "100m" - - acme-companion: - image: nginxproxy/acme-companion - container_name: nginx-proxy-acme + - /var/run/docker.sock:/var/run/docker.sock environment: - - DEFAULT_EMAIL=noreply@gno.land - volumes_from: - - nginx-proxy - volumes: - - certs:/etc/nginx/certs:rw - - acme:/etc/acme.sh - - /var/run/docker.sock:/var/run/docker.sock:ro - logging: - driver: "json-file" - options: - max-file: "10" - max-size: "100m" + WATCHTOWER_HTTP_API_TOKEN: "mytoken" -volumes: - conf: - vhost: - html: - certs: - acme: + restarter: + image: docker:cli + restart: unless-stopped + entrypoint: [ "/bin/sh", "-c" ] + working_dir: "/app" + volumes: + - ".:/app" + - "/var/run/docker.sock:/var/run/docker.sock" + command: + - | + while true; do + if [ "$$(date +'%H:%M')" = '22:00' ]; then + docker compose restart gnoland + fi + sleep 60 + done diff --git a/misc/deployments/staging.gno.land/gnoland.entrypoint.sh b/misc/deployments/staging.gno.land/gnoland.entrypoint.sh new file mode 100755 index 00000000000..90957e92da8 --- /dev/null +++ b/misc/deployments/staging.gno.land/gnoland.entrypoint.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env sh + +set -ex + +MONIKER=${MONIKER:-"gnode"} +P2P_LADDR=${P2P_LADDR:-"tcp://0.0.0.0:26656"} +RPC_LADDR=${RPC_LADDR:-"tcp://0.0.0.0:26657"} + +CHAIN_ID=${CHAIN_ID:-"staging"} + +rm -rfv ./gnoland-data genesis.json + +gnoland config init +gnoland secrets init + +gnoland config set moniker "${MONIKER}" +gnoland config set rpc.laddr "${RPC_LADDR}" +gnoland config set p2p.laddr "${P2P_LADDR}" + +exec gnoland start \ + --skip-failing-genesis-txs \ + --chainid="${CHAIN_ID}" \ + --lazy 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/docker-integration/integration_test.go b/misc/docker-integration/integration_test.go index 1142709cc16..973cb386e9b 100644 --- a/misc/docker-integration/integration_test.go +++ b/misc/docker-integration/integration_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/std" @@ -55,7 +56,10 @@ func runSuite(t *testing.T, tempdir string) { var acc gnoland.GnoAccount dockerExec_gnokeyQuery(t, "auth/accounts/"+test1Addr, &acc) require.Equal(t, test1Addr, acc.Address.String(), "test1 account not found") - minCoins := std.MustParseCoins("9990000000000ugnot") // This value is chosen arbitrarily and may not be optimal. Feel free to update it to a more suitable amount + + // This value is chosen arbitrarily and may not be optimal. + // Feel free to update it to a more suitable amount. + minCoins := std.MustParseCoins(ugnot.ValueString(9990000000000)) require.True(t, acc.Coins.IsAllGTE(minCoins), "test1 account coins expected at least %s, got %s", minCoins, acc.Coins) 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/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/fail/fail.go b/tm2/pkg/bft/fail/fail.go index 607084f484f..c56f43d7d89 100644 --- a/tm2/pkg/bft/fail/fail.go +++ b/tm2/pkg/bft/fail/fail.go @@ -4,31 +4,33 @@ import ( "fmt" "os" "strconv" + "sync" ) -func envSet() int { +func setFromEnv() { callIndexToFailS := os.Getenv("FAIL_TEST_INDEX") if callIndexToFailS == "" { - return -1 + callIndexToFail = -1 } else { var err error - callIndexToFail, err := strconv.Atoi(callIndexToFailS) + callIndexToFail, err = strconv.Atoi(callIndexToFailS) if err != nil { - return -1 + callIndexToFail = -1 } - return callIndexToFail } } -// Fail when FAIL_TEST_INDEX == callIndex -var callIndex int // indexes Fail calls +var ( + callIndex int // indexes Fail calls + callIndexToFail int // index of call which should fail + callIndexToFailOnce sync.Once // sync.Once to set the value of the above +) +// Fail exits the program when after being called the same number of times as +// that passed as the FAIL_TEST_INDEX environment variable. func Fail() { - callIndexToFail := envSet() - if callIndexToFail < 0 { - return - } + callIndexToFailOnce.Do(setFromEnv) if callIndex == callIndexToFail { Exit() 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/lib/client/http/client.go b/tm2/pkg/bft/rpc/lib/client/http/client.go index 290310cea31..aa4fc5c5392 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client.go @@ -18,6 +18,9 @@ const ( protoHTTP = "http" protoHTTPS = "https" protoTCP = "tcp" + + portHTTP = "80" + portHTTPS = "443" ) var ( @@ -57,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 } @@ -230,9 +233,9 @@ func parseRemoteAddr(remoteAddr string) (string, string) { if !strings.Contains(address, ":") { switch protocol { case protoHTTPS: - address += ":443" + address += ":" + portHTTPS case protoHTTP, protoTCP: - address += ":80" + address += ":" + portHTTP default: // noop } } 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 cfe69c3015c..4ccbfdc2d1e 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client_test.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client_test.go @@ -108,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) - assert.Equal(t, request.ID, resp.ID) - assert.Equal(t, request.JSONRPC, resp.JSONRPC) - assert.Nil(t, resp.Result) - assert.Nil(t, resp.Error) + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFn() + + // 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/state/execution.go b/tm2/pkg/bft/state/execution.go index 8b461cdbf6c..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, 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/crypto/bech32_test.go b/tm2/pkg/crypto/bech32_test.go index f5bc3e9ed7c..d0d7ee92898 100644 --- a/tm2/pkg/crypto/bech32_test.go +++ b/tm2/pkg/crypto/bech32_test.go @@ -1,6 +1,7 @@ package crypto_test import ( + "encoding/json" "math/rand" "testing" @@ -59,6 +60,7 @@ func TestRandBech32AddrConsistency(t *testing.T) { addr := crypto.AddressFromBytes(pub.Address().Bytes()) testMarshal(t, addr, amino.Marshal, amino.Unmarshal) testMarshal(t, addr, amino.MarshalJSON, amino.UnmarshalJSON) + testMarshal(t, addr, json.Marshal, json.Unmarshal) str := addr.String() res, err := crypto.AddressFromBech32(str) diff --git a/tm2/pkg/crypto/crypto.go b/tm2/pkg/crypto/crypto.go index e7089ca579b..7757b75354e 100644 --- a/tm2/pkg/crypto/crypto.go +++ b/tm2/pkg/crypto/crypto.go @@ -2,6 +2,7 @@ package crypto import ( "bytes" + "encoding/json" "fmt" "github.com/gnolang/gno/tm2/pkg/bech32" @@ -53,11 +54,25 @@ func AddressFromBytes(bz []byte) (ret Address) { return } +func (addr Address) MarshalJSON() ([]byte, error) { + b := AddressToBech32(addr) + return []byte(`"` + b + `"`), nil +} + +func (addr *Address) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + return addr.UnmarshalAmino(s) +} + func (addr Address) MarshalAmino() (string, error) { return AddressToBech32(addr), nil } func (addr *Address) UnmarshalAmino(b32str string) (err error) { + // NOTE: also used to unmarshal normal JSON, through UnmarshalJSON. if b32str == "" { return nil // leave addr as zero. } diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index 70dafaaef90..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" @@ -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/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 2afccf9141c..7e67392ebe7 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" @@ -210,6 +211,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 +221,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/secp256k1/secp256k1_nocgo.go b/tm2/pkg/crypto/secp256k1/secp256k1_nocgo.go index ccf2104a8d3..3e89515294d 100644 --- a/tm2/pkg/crypto/secp256k1/secp256k1_nocgo.go +++ b/tm2/pkg/crypto/secp256k1/secp256k1_nocgo.go @@ -15,10 +15,7 @@ import ( func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { priv, _ := btcec.PrivKeyFromBytes(privKey[:]) - sig, err := ecdsa.SignCompact(priv, crypto.Sha256(msg), false) // ref uncompressed pubkey - if err != nil { - return nil, err - } + sig := ecdsa.SignCompact(priv, crypto.Sha256(msg), false) // ref uncompressed pubkey // remove compact sig recovery code byte at the beginning return sig[1:], nil diff --git a/tm2/pkg/sdk/abci.go b/tm2/pkg/sdk/abci.go index a9cd14e9ed3..0b86518f0b9 100644 --- a/tm2/pkg/sdk/abci.go +++ b/tm2/pkg/sdk/abci.go @@ -16,3 +16,12 @@ type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) abci.ResponseBeg // Note: applications which set create_empty_blocks=false will not have regular block timing and should use // e.g. BFT timestamps rather than block height for any periodic EndBlock logic type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBlock + +// BeginTxHook is a BaseApp-specific hook, called to modify the context with any +// additional application-specific information, before running the messages in a +// transaction. +type BeginTxHook func(ctx Context) Context + +// EndTxHook is a BaseApp-specific hook, called after all the messages in a +// transaction have terminated. +type EndTxHook func(ctx Context, result Result) diff --git a/tm2/pkg/sdk/bank/msgs.go b/tm2/pkg/sdk/bank/msgs.go index d87656b8b7b..378af29f544 100644 --- a/tm2/pkg/sdk/bank/msgs.go +++ b/tm2/pkg/sdk/bank/msgs.go @@ -39,7 +39,7 @@ func (msg MsgSend) ValidateBasic() error { return std.ErrInvalidAddress("missing recipient address") } if !msg.Amount.IsValid() { - return std.ErrInvalidCoins("send amount is invalid: " + msg.Amount.String()) + return std.ErrInvalidCoins(msg.Amount.String()) } if !msg.Amount.IsAllPositive() { return std.ErrInsufficientCoins("send amount must be positive") diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index 0fa26b817e1..671f18cf058 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -42,6 +42,9 @@ type BaseApp struct { beginBlocker BeginBlocker // logic to run before any txs endBlocker EndBlocker // logic to run after all txs, and to determine valset changes + beginTxHook BeginTxHook // BaseApp-specific hook run before running transaction messages. + endTxHook EndTxHook // BaseApp-specific hook run after running transaction messages. + // -------------------- // Volatile state // checkState is set on initialization and reset on Commit. @@ -622,11 +625,12 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res ctx = ctx.WithEventLogger(NewEventLogger()) msgLogs := make([]string, 0, len(msgs)) - data := make([]byte, 0, len(msgs)) - err := error(nil) - events := []Event{} + var ( + err error + events = []Event{} + ) // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. for i, msg := range msgs { @@ -657,6 +661,7 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res fmt.Sprintf("msg:%d,success:%v,log:%s,events:%v", i, false, msgResult.Log, events)) err = msgResult.Error + events = nil break } @@ -664,7 +669,10 @@ 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()...) + + if err == nil { + events = append(events, ctx.EventLogger().Events()...) + } result.Error = ABCIError(err) result.Data = data @@ -820,6 +828,11 @@ 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) + + if app.beginTxHook != nil { + runMsgCtx = app.beginTxHook(runMsgCtx) + } + result = app.runMsgs(runMsgCtx, msgs, mode) result.GasWanted = gasWanted @@ -828,6 +841,10 @@ func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) return result } + if app.endTxHook != nil { + app.endTxHook(runMsgCtx, result) + } + // only update state if all messages pass if result.IsOK() { msCache.MultiWrite() diff --git a/tm2/pkg/sdk/baseapp_test.go b/tm2/pkg/sdk/baseapp_test.go index 1680b99a5c6..c8884533b30 100644 --- a/tm2/pkg/sdk/baseapp_test.go +++ b/tm2/pkg/sdk/baseapp_test.go @@ -19,9 +19,9 @@ import ( "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/sdk/testutils" "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/iavl" - store "github.com/gnolang/gno/tm2/pkg/store/types" ) var ( @@ -199,6 +199,47 @@ func TestLoadVersionInvalid(t *testing.T) { require.Error(t, err) } +func TestOptionSetters(t *testing.T) { + t.Parallel() + + tt := []struct { + // Calling BaseApp.[method]([value]) should change BaseApp.[fieldName] to [value]. + method string + fieldName string + value any + }{ + {"SetName", "name", "hello"}, + {"SetAppVersion", "appVersion", "12345"}, + {"SetDB", "db", memdb.NewMemDB()}, + {"SetCMS", "cms", store.NewCommitMultiStore(memdb.NewMemDB())}, + {"SetInitChainer", "initChainer", func(Context, abci.RequestInitChain) abci.ResponseInitChain { panic("not implemented") }}, + {"SetBeginBlocker", "beginBlocker", func(Context, abci.RequestBeginBlock) abci.ResponseBeginBlock { panic("not implemented") }}, + {"SetEndBlocker", "endBlocker", func(Context, abci.RequestEndBlock) abci.ResponseEndBlock { panic("not implemented") }}, + {"SetAnteHandler", "anteHandler", func(Context, Tx, bool) (Context, Result, bool) { panic("not implemented") }}, + {"SetBeginTxHook", "beginTxHook", func(Context) Context { panic("not implemented") }}, + {"SetEndTxHook", "endTxHook", func(Context, Result) { panic("not implemented") }}, + } + + for _, tc := range tt { + t.Run(tc.method, func(t *testing.T) { + t.Parallel() + + var ba BaseApp + rv := reflect.ValueOf(&ba) + + rv.MethodByName(tc.method).Call([]reflect.Value{reflect.ValueOf(tc.value)}) + changed := rv.Elem().FieldByName(tc.fieldName) + + if reflect.TypeOf(tc.value).Kind() == reflect.Func { + assert.Equal(t, reflect.ValueOf(tc.value).Pointer(), changed.Pointer(), "%s(%#v): function value should have changed", tc.method, tc.value) + } else { + assert.True(t, reflect.ValueOf(tc.value).Equal(changed), "%s(%#v): wanted %v got %v", tc.method, tc.value, tc.value, changed) + } + assert.False(t, changed.IsZero(), "%s(%#v): field's new value should not be zero value", tc.method, tc.value) + }) + } +} + func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID store.CommitID) { t.Helper() @@ -272,6 +313,12 @@ func TestBaseAppOptionSeal(t *testing.T) { require.Panics(t, func() { app.SetAnteHandler(nil) }) + require.Panics(t, func() { + app.SetBeginTxHook(nil) + }) + require.Panics(t, func() { + app.SetEndTxHook(nil) + }) } func TestSetMinGasPrices(t *testing.T) { @@ -927,7 +974,6 @@ func TestMaxBlockGasLimits(t *testing.T) { } for i, tc := range testCases { - fmt.Printf("debug i: %v\n", i) tx := tc.tx // reset the block gas diff --git a/tm2/pkg/sdk/context.go b/tm2/pkg/sdk/context.go index 0e1021e0174..63c5a50f8eb 100644 --- a/tm2/pkg/sdk/context.go +++ b/tm2/pkg/sdk/context.go @@ -147,31 +147,21 @@ func (c Context) WithEventLogger(em *EventLogger) Context { return c } -// WithValue is deprecated, provided for backwards compatibility -// Please use +// WithValue is shorthand for: // -// ctx = ctx.WithContext(context.WithValue(ctx.Context(), key, false)) +// c.WithContext(context.WithValue(c.Context(), key, value)) // -// instead of -// -// ctx = ctx.WithValue(key, false) -// -// NOTE: why? +// It adds a value to the [context.Context]. func (c Context) WithValue(key, value interface{}) Context { c.ctx = context.WithValue(c.ctx, key, value) return c } -// Value is deprecated, provided for backwards compatibility -// Please use -// -// ctx.Context().Value(key) -// -// instead of +// Value is shorthand for: // -// ctx.Value(key) +// c.Context().Value(key) // -// NOTE: why? +// It retrieves a value from the [context.Context]. func (c Context) Value(key interface{}) interface{} { return c.ctx.Value(key) } diff --git a/tm2/pkg/sdk/options.go b/tm2/pkg/sdk/options.go index f174b5501a2..b9840a7510b 100644 --- a/tm2/pkg/sdk/options.go +++ b/tm2/pkg/sdk/options.go @@ -85,3 +85,17 @@ func (app *BaseApp) SetAnteHandler(ah AnteHandler) { } app.anteHandler = ah } + +func (app *BaseApp) SetBeginTxHook(beginTx BeginTxHook) { + if app.sealed { + panic("SetBeginTxHook() on sealed BaseApp") + } + app.beginTxHook = beginTx +} + +func (app *BaseApp) SetEndTxHook(endTx EndTxHook) { + if app.sealed { + panic("SetEndTxHook() on sealed BaseApp") + } + app.endTxHook = endTx +}