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/lint_template.yml b/.github/workflows/lint_template.yml index 098650c1df2..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: @@ -25,3 +25,4 @@ jobs: 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/nightlies.yml b/.github/workflows/nightlies.yml index 6e3e3e58935..0110801dc93 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -38,8 +38,9 @@ jobs: - 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/releaser-master.yml b/.github/workflows/releaser-master.yml index 3ed5353ec89..96a622e3272 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -39,8 +39,9 @@ jobs: - 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 9148d9ac15c..f3317419510 100644 --- a/.github/workflows/releaser.yml +++ b/.github/workflows/releaser.yml @@ -38,7 +38,7 @@ jobs: - 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/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/const/const.gno b/const/const.gno new file mode 100644 index 00000000000..76f538428d2 --- /dev/null +++ b/const/const.gno @@ -0,0 +1,8 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + const length = len(&exp) + + println(length) +} diff --git a/const/const.go b/const/const.go new file mode 100644 index 00000000000..76f538428d2 --- /dev/null +++ b/const/const.go @@ -0,0 +1,8 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + const length = len(&exp) + + println(length) +} diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 69768fc7e26..c56c0b7d425 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -5,7 +5,7 @@ go 1.22 toolchain go1.22.4 require ( - github.com/gnolang/faucet v0.3.1 + 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 diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 2056f4f89e9..1508cdae1e6 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -45,8 +45,8 @@ 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.3.1 h1:BalLeZNYk9v/+jW6d+2ox1SMWgHtDHZ+9rSD71h4Xcg= -github.com/gnolang/faucet v0.3.1/go.mod h1:/wbw9h4ooMzzyNBuM0X+ol7CiPH2OFjAFF3bYAXqA7U= +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= 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/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/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/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno index 7b3254ea505..2776e93dca9 100644 --- a/examples/gno.land/p/demo/uassert/uassert.gno +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -379,46 +379,85 @@ func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool { 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() - switch val := obj.(type) { - case string: - if val != "" { - return fail(t, msgs, "uassert.Empty: not empty string: %s", val) - } - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - if val != 0 { - return fail(t, msgs, "uassert.Empty: not empty number: %d", val) + + isNumber, isEmpty := isNumberEmpty(obj) + if isNumber { + if !isEmpty { + return fail(t, msgs, "uassert.Empty: not empty number: %d", obj) } - case std.Address: - var zeroAddr std.Address - if val != zeroAddr { - return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val)) + } 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") } - default: - return fail(t, msgs, "uassert.Empty: unsupported type") } return true } func NotEmpty(t TestingT, obj interface{}, msgs ...string) bool { t.Helper() - switch val := obj.(type) { - case string: - if val == "" { - return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val) - } - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - if val == 0 { - return fail(t, msgs, "uassert.NotEmpty: empty number: %d", val) - } - case std.Address: - var zeroAddr std.Address - if val == zeroAddr { - return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val)) + 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") } - 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 5ead848fd15..7862eca7305 100644 --- a/examples/gno.land/p/demo/uassert/uassert_test.gno +++ b/examples/gno.land/p/demo/uassert/uassert_test.gno @@ -218,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 @@ -335,6 +336,7 @@ func TestNotEmpty(t *testing.T) { {"", false}, {0, false}, {int(0), false}, + {int32(0), false}, {int64(0), false}, {uint(0), false}, {std.Address(""), false}, diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index eb6f44d4026..072c98f3bd6 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,6 +1,7 @@ module gno.land/r/demo/art/gnoface 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/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 cb2ec58b665..c208ad421c9 100644 --- a/examples/gno.land/r/gnoland/home/gno.mod +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -5,4 +5,5 @@ require ( gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/demo/ui v0.0.0-latest gno.land/r/gnoland/blog v0.0.0-latest + 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 62984711d79..921492d81b4 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -7,6 +7,7 @@ import ( "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 @@ -35,7 +36,7 @@ func Render(_ string) string { dom.Body.Append( ui.Columns{3, []ui.Element{ lastBlogposts(4), - upcomingEvents(4), + upcomingEvents(), lastContributions(4), }}, ) @@ -68,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), } } @@ -81,11 +82,11 @@ 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), } } @@ -268,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) diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 2b0a802718f..b70b22c80af 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -56,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) @@ -68,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 index 34356b93349..4f21b90a3c2 100644 --- a/examples/gno.land/r/gnoland/home/overide_filetest.gno +++ b/examples/gno.land/r/gnoland/home/overide_filetest.gno @@ -21,4 +21,4 @@ func main() { // Output: // Hello World! -// r: unauthorized; caller is not owner +// r: ownable: caller is not owner 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/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/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..37c63458515 100644 --- a/gno.land/pkg/gnoweb/views/funcs.html +++ b/gno.land/pkg/gnoweb/views/funcs.html @@ -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/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 0d4581377c2..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 { diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 36130ccbf4d..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, }) } diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 5a710723b86..e3f5111d15c 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1339,6 +1339,17 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } + // } else if fv.PkgPath == uversePkgPath && fv.Name == "len" { + // if len(n.Args) == 1 { + // // If the argument is a const string, set it to const expr. + // if arg0, ok := n.Args[0].(*ConstExpr); ok && evalStaticTypeOf(store, last, n.Args[0]).Kind() == StringKind { + // ct := evalStaticTypeOf(store, last, n.Args[0]) + // convertConst(store, last, arg0, ct) + // fmt.Println(reflect.TypeOf(n)) + // evalConst(store, last, n.Func) + // } + // } + // } } // Continue with general case. @@ -1503,6 +1514,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 { @@ -2150,6 +2168,11 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: may or may not be a *ConstExpr, // but if not, make one now. for i, vx := range n.Values { + if !isConst(vx) { + if _, ok := vx.(*CallExpr); !ok && !isSpecialConstValueCase(store, last, vx) { + panic(fmt.Sprintf("const expression is not valid %s", vx.String())) + } + } n.Values[i] = evalConst(store, last, vx) } } else { @@ -2794,8 +2817,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 } } } @@ -2906,6 +2931,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 @@ -3197,11 +3308,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() { @@ -3209,15 +3320,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) @@ -3226,7 +3346,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 } diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 31025fef152..26e200e7c48 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,63 @@ func assertAssignableTo(xt, dt Type, autoNative bool) { } } +func isSpecialConstValueCase(store Store, last BlockNode, expr Expr) bool { + fmt.Println(reflect.TypeOf(expr)) + switch x := expr.(type) { + case *CallExpr: + if cx, ok := x.Func.(*ConstExpr); ok { + fv := cx.GetFunc() + if fv.PkgPath == uversePkgPath && fv.Name == "len" && len(x.Args) == 1 { + t := evalStaticTypeOf(store, last, x.Args[0]) + if _, ok := t.(*PointerType); ok { + t = t.Elem() + } + _, ok := x.Args[0].(*ConstExpr) + if (ok && t.Kind() == StringKind) || t.Kind() == ArrayKind { + return true + } + } + } + case *BasicLitExpr: + return true + default: + return false + } + return false +} + +// checkValConstType checks the type and values are valid for a ValueDecl of const. +func checkValConstType(d *ValueDecl) { + if d.Type != nil { + switch d.Type.(type) { + case *BasicLitExpr, *NameExpr, *constTypeExpr: + // Valid constant type expression, in case of NameExpr should evaluate if underlying type is a basic type + default: + panic(fmt.Sprintf("invalid type for const: %v", d.Type.String())) + } + } + for _, vx := range d.Values { + checkValConstValue(vx) + } +} + +// checkValConstValue validate an expr is valid as a value for a const +func checkValConstValue(expr Expr) { + switch x := expr.(type) { + case *BasicLitExpr, *NameExpr: + // Valid constant type expression, in case of NameExpr should evaluate if underlying type is a basic type + case *BinaryExpr: + checkValConstValue(x.Left) + checkValConstValue(x.Right) + case *UnaryExpr: + checkValConstValue(x.X) + case *CallExpr: + checkValConstValue(x.Func) + default: + panic(fmt.Sprintf("invalid value for const: %v", expr.String())) + } +} + // checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt. func checkValDefineMismatch(n Node) { var ( 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/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/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..5ad121f515b --- /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/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/test/const.gno b/test/const.gno new file mode 100644 index 00000000000..864f196895d --- /dev/null +++ b/test/const.gno @@ -0,0 +1,8 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + const length = len(exp) + + println(length) +} diff --git a/test/test.gno b/test/test.gno new file mode 100644 index 00000000000..8ac65205944 --- /dev/null +++ b/test/test.gno @@ -0,0 +1,8 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + + const length = len(exp) + println(length) +}