commit 62bd7bb1535eb5bc2ba6c700fcdacaf2e2ee01ed Author: Tutus Development Date: Fri Dec 19 14:23:04 2025 +0000 Initial Tutus chain fork from NeoGo - Renamed module to github.com/tutus-one/tutus-chain - Created Tutus network configurations - Updated Makefile for tutus binary - Updated service templates - Added Tutus README diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml new file mode 100644 index 0000000..2413ec0 --- /dev/null +++ b/.docker/docker-compose.yml @@ -0,0 +1,74 @@ +networks: + default: + name: neo_go_network + ipam: + config: + - subnet: 172.200.0.0/24 + gateway: 172.200.0.254 + +volumes: + volume_chain: + driver: local + +services: + node_one: + container_name: neo_go_node_one + image: env_neo_go_image + command: "node --config-path /config --privnet --force-timestamp-logs" + volumes: + - ../config/protocol.privnet.docker.one.yml:/config/protocol.privnet.yml + - ./wallets/wallet1.json:/wallet1.json + - volume_chain:/chains + ports: + - 20333:20333 + - 30333:30333 + - 20001:20001 + node_two: + container_name: neo_go_node_two + image: env_neo_go_image + command: "node --config-path /config --privnet --force-timestamp-logs" + volumes: + - ../config/protocol.privnet.docker.two.yml:/config/protocol.privnet.yml + - ./wallets/wallet2.json:/wallet2.json + - volume_chain:/chains + ports: + - 20334:20334 + - 30334:30334 + - 20002:20002 + node_three: + container_name: neo_go_node_three + image: env_neo_go_image + command: "node --config-path /config --privnet --force-timestamp-logs" + volumes: + - ../config/protocol.privnet.docker.three.yml:/config/protocol.privnet.yml + - ./wallets/wallet3.json:/wallet3.json + - volume_chain:/chains + ports: + - 20335:20335 + - 30335:30335 + - 20003:20003 + node_four: + container_name: neo_go_node_four + image: env_neo_go_image + command: "node --config-path /config --privnet --force-timestamp-logs" + volumes: + - ../config/protocol.privnet.docker.four.yml:/config/protocol.privnet.yml + - ./wallets/wallet4.json:/wallet4.json + - volume_chain:/chains + ports: + - 20336:20336 + - 30336:30336 + - 20004:20004 + node_single: + container_name: neo_go_node_single + image: env_neo_go_image + command: "node --config-path /config --privnet --force-timestamp-logs" + volumes: + - ../config/protocol.privnet.docker.single.yml:/config/protocol.privnet.yml + - ./wallets/wallet1_solo.json:/wallet1.json + - volume_chain:/chains + ports: + - 20333:20333 + - 30333:30333 + - 20001:20001 + diff --git a/.docker/privnet-entrypoint.ps1 b/.docker/privnet-entrypoint.ps1 new file mode 100644 index 0000000..94cd4fc --- /dev/null +++ b/.docker/privnet-entrypoint.ps1 @@ -0,0 +1,15 @@ +#!C:\Program Files\PowerShell\7\pwsh.EXE -File + +$bin = '/usr/bin/neo-go.exe' + +for ( $i = 0; $i -lt $args.count; $i++ ) { + if ($args[$i] -eq "node"){ + Write-Host "=> Try to restore blocks before running node" + if (($Env:ACC -ne $null) -and (Test-Path $Env:ACC -PathType Leaf)) { + & $bin db restore -p --config-path /config -i $Env:ACC + } + break + } +} + +& $bin $args diff --git a/.docker/privnet-entrypoint.sh b/.docker/privnet-entrypoint.sh new file mode 100755 index 0000000..3289c04 --- /dev/null +++ b/.docker/privnet-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +BIN=/usr/bin/neo-go + +case $@ in +"node"*) + echo "=> Try to restore blocks before running node" + if [ -f "$ACC" ]; then + gunzip --stdout "$ACC" >/privnet.acc + ${BIN} db restore -p --config-path /config -i /privnet.acc + fi + ;; +esac + +${BIN} "$@" diff --git a/.docker/wallets/wallet1.json b/.docker/wallets/wallet1.json new file mode 100644 index 0000000..647eb85 --- /dev/null +++ b/.docker/wallets/wallet1.json @@ -0,0 +1,55 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", + "key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY", + "label": "", + "contract": { + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", + "key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/.docker/wallets/wallet1_solo.json b/.docker/wallets/wallet1_solo.json new file mode 100644 index 0000000..7ebcf53 --- /dev/null +++ b/.docker/wallets/wallet1_solo.json @@ -0,0 +1,72 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", + "key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY", + "label": "", + "contract": { + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", + "key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP", + "key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY", + "label": "", + "contract": { + "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/.docker/wallets/wallet2.json b/.docker/wallets/wallet2.json new file mode 100644 index 0000000..ad3e879 --- /dev/null +++ b/.docker/wallets/wallet2.json @@ -0,0 +1,55 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "NMUedC8TSV2rE17wGguSvPk9XcmHSaT275", + "key": "6PYSYoZaxqDu5vqvm7yUFT3sPJJFwyLyYDnp8zwj1YVPcBWxacz64bNX59", + "label": "", + "contract": { + "script": "DCECEDp/fdAWVYWX95YNJ8UWpDlP2Wi55lFV60sBPkBAQG5BVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", + "key": "6PYSYoZaxqDu5vqvm7yUFT3sPJJFwyLyYDnp8zwj1YVPcBWxacz64bNX59", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/.docker/wallets/wallet3.json b/.docker/wallets/wallet3.json new file mode 100644 index 0000000..de6446c --- /dev/null +++ b/.docker/wallets/wallet3.json @@ -0,0 +1,55 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo", + "key": "6PYVxMnzPMFTYY16xRvXm2SJcvaChabLzaARAb1Mmej9U7rYLYWMSPtfam", + "label": "", + "contract": { + "script": "DCED2QwH32PmkM53kS4Qq1GsyUS2aGAje2CMT4+DCece5plBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", + "key": "6PYVxMnzPMFTYY16xRvXm2SJcvaChabLzaARAb1Mmej9U7rYLYWMSPtfam", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/.docker/wallets/wallet4.json b/.docker/wallets/wallet4.json new file mode 100644 index 0000000..95d6f33 --- /dev/null +++ b/.docker/wallets/wallet4.json @@ -0,0 +1,55 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "NPrB7BmTMYxf9UVroJp4RQExM9tqKmsHTz", + "key": "6PYX8eELiDduPW3RGiZxZNmG6KtWtXkyRFi47f8w6quEBpRkpBPxH5u5AP", + "label": "", + "contract": { + "script": "DCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWJBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", + "key": "6PYX8eELiDduPW3RGiZxZNmG6KtWtXkyRFi47f8w6quEBpRkpBPxH5u5AP", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6ebc433 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +chains diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..50e5f83 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @AnnaShaleva @roman-khimov diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a27a88a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +### Problem + +... + +### Solution + +... diff --git a/.github/logo_dark.png b/.github/logo_dark.png new file mode 100644 index 0000000..32a3a3e Binary files /dev/null and b/.github/logo_dark.png differ diff --git a/.github/logo_light.png b/.github/logo_light.png new file mode 100644 index 0000000..366cd7e Binary files /dev/null and b/.github/logo_light.png differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c6092c1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,152 @@ +name: Build + +on: + pull_request: + branches: + - master + types: [opened, synchronize] + paths-ignore: + - 'scripts/**' + - '**/*.md' + push: + # Build for the master branch. + branches: + - master + release: + # Publish released commit as Docker `latest` and `git_revision` images. + types: + - published + workflow_dispatch: + inputs: + ref: + description: 'Ref to build CLI for Ubuntu and Windows Server Core [default: latest master; examples: v0.92.0, 0a4ff9d3e4a9ab432fd5812eb18c98e03b5a7432]' + required: false + default: '' + push_image: + description: 'Push images to DockerHub [default: false; examples: true, false]' + required: false + default: 'false' + use_latest_tag: + description: 'Use `latest` tag while pushing images to DockerHub (applied to Ubuntu image only) [default: false; examples: true, false]' + required: false + default: 'false' + +jobs: + build_cli: + name: Build CLI + runs-on: ${{matrix.os.name}} + strategy: + matrix: + os: [{ name: ubuntu-latest, bin-name: linux }, { name: windows-latest, bin-name: windows }, { name: macos-latest, bin-name: darwin }] + arch: [amd64, arm64] + exclude: + - os: { name: windows-latest, bin-name: windows } + arch: 'arm64' + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + # Allows to fetch all history for all branches and tags. Need this for proper versioning. + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Build CLI + run: make build + env: + GOARCH: ${{ matrix.arch }} + + - name: Rename CLI binary + run: mv ./bin/neo-go* ./bin/neo-go-${{ matrix.os.bin-name }}-${{ matrix.arch }}${{ (matrix.os.bin-name == 'windows' && '.exe') || '' }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: neo-go-${{ matrix.os.bin-name }}-${{ matrix.arch }} + path: ./bin/neo-go* + if-no-files-found: error + + - name: Attach binary to the release as an asset + if: ${{ github.event_name == 'release' }} + run: gh release upload ${{ github.event.release.tag_name }} ./bin/neo-go-${{ matrix.os.bin-name }}-${{ matrix.arch }}${{ (matrix.os.bin-name == 'windows' && '.exe') || '' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build_image: + needs: build_cli + name: Build and push docker image + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Set vars + id: setvars + run: make gh-docker-vars >> $GITHUB_OUTPUT + + - name: Set latest tag + id: setlatest + if: ${{ (github.event_name == 'release' && github.event.release.target_commitish == 'master') || (github.event_name == 'workflow_dispatch' && github.event.inputs.use_latest_tag == 'true') }} + run: echo "latest=,${{ steps.setvars.outputs.repo }}:latest" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }} + platforms: linux/amd64,linux/arm64 + build-args: | + REPO=github.com/${{ github.repository }} + VERSION=${{ steps.setvars.outputs.version }} + tags: ${{ steps.setvars.outputs.repo }}:${{ steps.setvars.outputs.version }}${{ steps.setvars.outputs.suffix }}${{ steps.setlatest.outputs.latest }} + + build_image_wsc: + needs: build_cli + name: Build and push docker image (Windows Server Core) + runs-on: windows-latest + if: ${{ github.event_name != 'pull_request' }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + fetch-depth: 0 + + # For proper `deps` make target execution. + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Login to DockerHub + if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build Docker image + run: make image + + - name: Push image to registry + if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }} + run: make image-push diff --git a/.github/workflows/contribution_guidelines.yml b/.github/workflows/contribution_guidelines.yml new file mode 100644 index 0000000..d75d5c0 --- /dev/null +++ b/.github/workflows/contribution_guidelines.yml @@ -0,0 +1,11 @@ +name: Contribution guidelines + +on: + pull_request: + branches: + - master + +jobs: + commits_check_job: + name: DCO check + uses: nspcc-dev/.github/.github/workflows/dco.yml@master diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..07e63e5 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,197 @@ +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: + - master + types: [opened, synchronize] + paths-ignore: + - 'scripts/*.sh' + - '**/*.md' + workflow_dispatch: + +jobs: + lint: + name: 'Lint: NeoGo' + uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master + + lint_examples: + name: 'Lint: examples (${{ matrix.contract }})' + uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master + + strategy: + fail-fast: false + matrix: + contract: [ 'engine', 'events', 'iterator', 'nft-d', 'nft-nd', 'nft-nd-nns', 'oracle', + 'runtime', 'storage', 'timer', 'token', 'zkp/cubic_circuit', 'zkp/xor_compat'] + with: + workdir: examples/${{ matrix.contract }} + + lint_scripts: + name: 'Lint: scripts' + uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master + with: + workdir: scripts + + lint_interops: + name: 'Lint: interop' + uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master + with: + workdir: pkg/interop + + gomodcheck: + name: Check internal dependencies + runs-on: ubuntu-slim + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Check dependencies + run: | + ./scripts/check_deps.sh + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: Check go.mod is tidy + run: | + go mod tidy + if [[ $(git diff --name-only go.* | grep '' -c) != 0 ]]; then + echo "go mod tidy should be executed before the merge, following packages are unused or out of date:"; + git diff go.*; + exit 1; + fi + + codegencheck: + name: Check code generated with 'go generate' is up-to-date + runs-on: ubuntu-slim + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Install stringer + run: go install golang.org/x/tools/cmd/stringer@latest + + - name: Run go generate + run: go generate ./... + + - name: Check that autogenerated code is up-to-date + run: | + if [[ $(git diff --name-only | grep '' -c) != 0 ]]; then + echo "Fresh version of autogenerated code should be committed for the following files:"; + git diff --name-only; + exit 1; + fi + + codeql: + name: CodeQL + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + + test_cover: + name: Coverage + runs-on: ubuntu-latest + + env: + CGO_ENABLED: 0 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: 'true' + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + cache: true + + - name: Write coverage profile + run: DISABLE_NEOTEST_COVER=1 go test -timeout 15m -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/... + + - name: Upload coverage results to Codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true # if something is wrong on uploading codecov results, then this job will fail + files: ./coverage.txt + slug: nspcc-dev/neo-go + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + tests: + name: Run tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + go_versions: [ '1.24', '1.25' ] + exclude: + # Only latest Go version for Windows and MacOS. + - os: windows-latest + go_versions: '1.24' + - os: macos-latest + go_versions: '1.24' + # Exclude latest Go version for Ubuntu as Coverage uses it. + - os: ubuntu-latest + go_versions: '1.25' + fail-fast: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: 'true' + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go_versions }}' + + - name: Run tests + run: go test -timeout 15m -v -race ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..459a619 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Added by CoZ developers +vendor/ +bin/ +!examples/**/vendor + +# text editors +# vscode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +# goland +.idea/* +# emacs +*~ +TAGS + +# storage +/chains + +# patch +*.orig +*.rej + +# Coverage +coverage.txt +coverage.html + +# Compiler output +examples/*/*.nef +examples/*/*.json + +# Fuzzing testdata. +testdata/ +!cli/testdata +!internal/basicchain/testdata +!pkg/compiler/testdata +!pkg/config/testdata +!pkg/consensus/testdata +!pkg/services/rpcsrv/testdata +!pkg/services/notary/testdata +!pkg/services/oracle/testdata +!pkg/smartcontract/testdata +!cli/smartcontract/testdata +pkg/vm/testdata/fuzz +!pkg/vm/testdata +!pkg/wallet/testdata + +# Linter +.golangci.yml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..34736ca --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "pkg/vm/testdata/neo-vm"] + path = pkg/vm/testdata/neo-vm + url = https://github.com/neo-project/neo.git + branch = master diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f997d1e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3368 @@ +# Changelog + +This document outlines major changes between releases. + +## 0.114.0 "Chemosorption" (17 Nov 2025) + +This version fixes the state difference at block 11074661 of testnet caused by +improper handling of native NEP-17 token transfer. It also fixes a deadlock +caused by inability to terminate an expired iterator session. It's fully +compatible with the C# node 3.8+ and delivers a set of improvements for +compiler, NeoFS block storage audit and uploader along with a preview of Faun +changes. + +Deprecated functionality has been removed according to the schedule (`math.Max` +and `math.Min` interop functions along with `SessionExpirationTime` RPC server +config), consider upgrading affected smart contracts and node config +correspondingly. The default ports of Prometheus and pprof services have been +changed for mainnet config. Testnet node resynchronisation (or state reset to +block 11074660 for full nodes) is recommended but not required since the state +is the same at higher block (starting from 11091272), and the only real +difference now is two application logs for two transactions. + +New features: + * local analogs of `System.Storage.*` interop API added to Faun preview (#4031) + +Behavior changes: + * migrate to single threaded search in NeoFS block storage uploader (#4030) + * adjust Prometheus and pprof ports in default mainnet config (#4074) + * removal of deprecated `math.Max` and `math.Min` interop functions (#4083) + * removal of deprecated `SessionExpirationTime` RPC server config (#4083) + +Improvements: + * documentation updates (#4035, #4045, #4079) + * NeoFS SDK Go update to RC15 (#4028) + * dependencies upgrade (#4067) + * support assignment operators `&=`, `|=`, `>>=`, `<<=` in compiler (#4059) + * add an example of NeoFS usage to smart contracts (#4064) + * refactor NeoFS block storage uploader (#4070, #4080) + * improve NeoFS block storage audit CLI tool (#4019) + * properly handle circular reference in stackitem JSON serialization (#4044) + +Bugs fixed: + * RPC bindings generator generates incorrect bindings for NEP-22/NEP-31 (#4022, + #4024, #4048) + * panic in the compiler on missing external package in the imported one (#4027) + * execution error caused by iteration over nil slice (#4026) + * add source URL to NEF file on compilation in neotest (#4042) + * `getnextblockvalidator` RPC handler returns result only for enrollments + (#4047) + * negative native NEP-17 transfer doesn't lead to VM FAULT (#4073) + * inability to force-terminate expired iterator session (#4082) + +## 0.113.0 "Circumnavigation" (24 Sept 2025) + +This version includes an upgrade to Go 1.24 and adds a couple of new native +contract APIs to Faun hardfork preview. But what is more important, it delivers +a set of essential compiler updates and bug fixes. + +We strongly recommend smart contract developers to upgrade to this version and +review the list of compiler-related improvements. Also, the upgrade is +recommended to the users of `rpcclient` package to avoid possible problems with +node version unmarshalling after upcoming Faun hardfork activation. Node +operators may safely skip this update. + +Behavior changes: + * switch from go.etcd.io/bbolt to custom (and upgraded) nspcc-dev/bbolt (#4006) + +Improvements: + * `hexEncode` and `hexDecode` APIs of native StdLib contract added to Faun + preview (#4004) + * `getBlockedAccounts` API of native Policy contract added to Faun preview + (#4004) + * built-in `clear()` support in compiler (#3091) + * range over integers in compiler (#3525) + * Go 1.24 support (#4010) + * byte slice/integer conversion helpers added to SC interop module (#3983) + * NEP-32 support for `db dump` CLI command (#3987) + +Bugs fixed: + * invalid string representation of Faun hardfork (#4001) + * increment operator doesn't work on struct fields in compiler (#3981) + * panic on inlined function invocation in compiler (#4002) + * list items swap doesn't work as expected in compiler (#4005) + * panic on fetching map values with `ok` flag (#3716) + * inability to `go install` neo-go due to `replace` directive in go.mod (#4006) + * initialization statement of `switch` is ignored by compiler (#3991) + +## 0.112.0 "Hibernation" (29 Aug 2025) + +This version fixes state difference at block 8813651 of testnet caused by +improper `ABORTMSG` and `AASSERTMSG` arguments handling. It also introduces +support for a set of newly-added NEPs and ability to customize the list of +native contracts. + +DB resynchronisation (or state reset to block 8813650 for full nodes) is +required on testnet nodes upgrade. `SessionExpirationTime` RPC server setting is +deprecated and replaced by `SessionLifetime` of `Duration` type, consider +upgrading the node's config. `math.Min` and `math.Max` interop utilities are +deprecated and replaced by Go built-in `min` and `max` functions, consider +upgrading affected smart contracts. Also, some deprecated functionality has been +removed according to the schedule. + +New features: + * customizable native contracts (#3966) + * NEP-22/NEP-31 (contract update and destroy functionality) support in CLI, + `smartcontract` package and compiler (#3968, #3971) + * NEP-29/NEP-30 (contract `_deploy` and `verify` methods) support in + `smartcontract` package and compiler (#3978) + * NEP-32 support for `db restore` CLI command (unified chain dump format) + (#3974) + * mempool events RPC web-socket subscription (#3967) + +Behavior changes: + * `wallet candidate register` CLI command is migrated onto GAS transfer (#3973) + * `SessionExpirationTime` RPC server setting is deprecated and replaced by + `SessionLifetime` (#3953) + * `math.Min` and `math.Max` interop helpers are deprecated and replaced with + Go built-in min/max (#3984) + * deprecated functionality removal (`GetBlockHeader` and + `GetBlockHeaderVerbose` methods of RPC client) (#3995) + * RPC client's `Waiter.Wait()` is extended with context parameter (#3959) + +Improvements: + * `storage.KeyValue` interop type (#3982) + * `util convert` CLI command is extended with Base64 public key convertor + * stackitem conversion is supported for `rolemgmt.DesignationEvent` native + RoleManagement RPC binding (#3956) + * `getversion` RPC response is extended with `SaveInvocations`, + `KeepOnlyLatestState` and `RemoveUntraceableBlocks` settings (#3954) + * Go built-in min/max support in compiler (#3984) + * web-socket client connection errors improvement (#3975) + +Bugs fixed: + * panic on public key comparison with infinite operand (#3961) + * missing strict UTF-8 check for `ABORTMSG` and `ASSERTMSG` arguments (#3988) + +## 0.111.0 "Facilitation" (18 Jul 2025) + +We've decided to release one more v3.8.0-compatible version since the current +set of changes includes adaptive block time extension allowing to vary block +generation time from `TimePerBlock` to `MaxTimePerBlock`. In addition to that, +this version includes new audit module for NeoFS block storage, an optimisation +of light node synchronisation process, enhanced version of BoltDB and more. + +Some deprecated functionality has been removed according to the schedule, see +more details in the `Behaviour changes` section. Also, for those node operators +who would like to test the dynamic block time extension, new `MaxTimePerBlock` +option is added to the node's protocol configuration. Finally, configuration +changes have been introduced: starting from this version, +`RemoveUntraceableHeaders` mode is a part of `RemoveUntraceableBlocks` and +hence, should be removed from the node's configuration file in favour of +`RemoveUntraceableBlocks`. Also, `SessionExpirationTime` RPC server +configuration setting is replaced with `SessionLifetime` setting of type +`Duration`; `SkipIndexFilesSearch`, `IndexFileSize` and `IndexFileAttribute` +settings of NeoFSBlockFetcher service were dropped since index files are not +supported anymore. Please, upgrade your configuration accordingly. + +New features: + * `util audit-bin` CLI command for NeoFS block storage audit (#3943, #3945, + #3946) + * support of `GetBlockHeaderByIndex[Verbose]` RPC client API (#3939) + * `MaxTimePerblock` protocol configuration setting enabling dynamic block + acceptance interval (#3948) + +Behavior changes: + * index files support is removed from `util upload-bin` CLI command and + NeoFSBlockFetcher service (#3938) + * `RemoveUntraceableHeaders` application configuration setting is merged into + `RemoveUntraceableBlocks` (#3922) + * `GetBlockHeaderVerbose` RPC client API is deprecated and replaced by + `GetBlockHeaderByHashVerbose` (#3939) + * deprecated `NEP11Payable` and `NEP17Payable` aliases are removed from the + smartcontract package in favour of `NEP26StandardName` and + `NEP27StandardName` (#3955) + * `SessionExpirationTime` RPC server configuration setting is deprecated and + replaced by `SessionLifetime` setting of `Duration` type (#3953) + +Improvements: + * light node synchronisation doesn't require full headers sync anymore (#3922) + * BoltDB dependency is upgraded to custom (and faster!) version (#3936, #3937) + * dBFT dependency upgrade empowering dynamic block time extension (#3948) + +Bugs fixed: + * misses in blocks uploaded by `util upload-bin` CLI command (#3942) + * panic on attempt to close local RPC client (#3947) + +## 0.110.0 "Utilization" (10 Jun 2025) + +This version fixes state difference at block 6701925 of testnet. It also introduces +NeoFSStateFetcher service that allows to synchronize contract storage state at +particular height based on checkpoint data stored in NeoFS. Moreover, for smooth +RPC client upgrade experience, the next Faun hardfork is added in a preview mode +(although it doesn't contain any changes yet). Also, new `BroadcastTxsBatchDelay` +P2P application configuration setting is introduced preliminary for custom networks +setup to be able to customize the maximum transaction batch broadcast delay. + +T5 nodes require DB resynchronisation (or state reset to 6701924 block for full nodes), +mainnet nodes DB resync is not required at the moment of release. No configuration +changes required. + +New features: + * ability to synchronize contract storage from NeoFS snapshot (#3844) + * Faun hardfork is introduced in a preview mode, but doesn't include + any changes yet (#3931) + +Improvements: + * migration to NeoFS SearchV2 API (#3913) + * support for graceful stop at given height for NeoFSBlockFetcher service (#3901) + * support slice expressions for string in compiler (#3916) + * integrate ArchivalNode capability (#3899) + * refactor code to use Go built-ins (#3897, #3923) + * update documentation (#3924) + * `BroadcastTxsBatchDelay` application setting that allows to customize the time + limit for transaction batch collection before its broadcast (#3930) + +Bugs fixed: + * race in cache persistence logic (#3931) + * whitespaces in argument of StdLib's `base64Decode` and `base64UrlDecode` + methods lead to decoding error (#3928) + * multiple simultaneous connections to a single peer (#3911) + * node peer state unambiguity (#3911) + * failed MPT initialization after node restart (#3919) + * bugs in seed discovery logic (#3912) + * intermediate state synchronisation stages are not persisted to the DB (#3900) + * intermediate state jump stages are not persisted to the DB (#3895) + * excessive logging in Notary service (#3896) + * missing block height initialization for statesync module (#3933) + +## 0.109.1 "Transformation" (14 May 2025) + +An urgent patch version that fixes state difference at block 5894663 of testnet. +It also contains a couple of other bug fixes including CommitteeChanged events +emission (that affects application execution results compatibility with C# node, +but likely not critical for anyone). + +DB resynchronisation for testnet nodes is required (operators of full node may +use `neo-go db reset` command to reset the node's state to height 5894662 and +avoid full chain resynchronisation). Mainnet DB resynchronisation is not +needed. + +Behavior changes: + * `subscribe` and `unsubscribe` requests will be served sequentially by web-socket + RPC client (#3893) + +Bugs fixed: + * CommitteeChanged event of native NeoToken is emitted irrespective of + Cockatrice fork (#3892) + * GasPerBlock update is applied starting from the next block (#3891) + * panic in subscriptions module of web-socket RPC client (#3893) + +## 0.109.0 "Separation" (01 May 2025) + +Long-awaited version that is fully compatible with the C# node 3.8.0. It extends +and enables Echidna hardfork, introduces a set of RPC extensions and new node +configuration settings and fixes a couple of critical bugs. + +Node operators should review and update node configuration. For N3 chains Echidna +hardfork is enabled at block `7300000` of Mainnet and `5870000` block of Testnet; +NeoFS Mainnet hardforks schedule is changed, see the updated node configuration +for reference; for NeoFS Testnet Echidna is enabled starting from the genesis +block. N3 Mainnet/Testnet nodes DB resynchronization is not needed, NeoFS chains +resynchronization is required. + +New features: + * block generation time is a part of native Policy contract + (`setMillisecondsPerBlock` and `getMillisecondsPerBlock` APIs) starting + from Echidna HF (#3835) + * `util upload-state` CLI command allowing to upload contract storage items + to NeoFS (#3808, #3845) + * `verifyWithEd25519` method support in CryptoLib native contract starting + from Echidna HF (#3852, #3853) + * `MaxWebSocketFeeds` node parameter allowing to configure maximum number + of RPC server subscriptions (#3828) + * `invokecontainedscript` RPC API allowing to invoke script in a + customizable execution environment (#3839) + * `LogEncoding` and `LogTimestamp` node parameters allowing to customize logs + output format and timestamps (#3846) + * `MaxValidUntilBlockIncrement` setting is a part of native Policy contract + (`setMaxValidUntilBlockIncrement` and `getMaxValidUntilBlockIncrement` APIs) + starting from Echidna HF (#3849) + * `MaxTraceableBlocks` setting is a part of native Policy contract + (`setMaxTraceableBlocks` and `getMaxTraceableBlocks` APIs) starting from + Echidna HF (#3858) + * `recoverSecp256K1` method support in CryptoLib native contract starting + from Echidna HF (#3863) + * `isContract` method support in ContractManagement native contract starting + from Echidna HF (#3867) + * `DisableCompression` node parameter allowing to disable P2P payloads + compression (#3881) + * `NotaryAssisted` attribute support is moved from `P2PSignatureExtensions` + under Echidna HF (#3854) + * Notary native contract activation is moved from `P2PSignatureExtensions` + under Echidna HF (#3478) + * Echidna HF is stable now (#3851) + +Behavior changes: + * restricted number of allowed contract notifications starting from Echidna + HF (#3640) + * NeoFS networks configuration update and forks reschedule (#3833, #3851) + +Improvements: + * RPC session iterator expansion extension (#3827) + * optimize VM script execution in case of disabled coverage (#3855) + * replace timers with tickers (#3861) + * extend NotaryAssisted attribute verification logic (#3865) + * `ErrConnClosedByUser` error designated to distinguish case when connection + is closed by RPC client user (#3868) + * Go 1.23 upgrade (#3847) + * NeoFS SDK Go update to RC13+ (#3847, #3870) + +Bugs fixed: + * NPE on `getpeers` RPC request serving (#3880) + * inability to decode genesis block header fetched from NeoFS (#3819) + * insufficient nesting depth restriction of transaction witness condition + (#3815) + * improper handling of empty filter for `getblocknotifications` RPC API + on the RPC client side (#3820) + * native contracts can't be initialized from non-genesis block (#3837) + * invalid signature of `base64UrlEncode` and `base64UrlDecode` (#3862) + * NPE on NeoFS BlockFetcher service shutdown (#3870) + +## 0.108.1 "Revalidation" (13 Feb 2025) + +An urgent fix for a very old behavior difference with C# node in Rules witness +condition parsing. It suddenly affected testnet compatibility at block 5450030 +and made the chain unprocessable by NeoGo. Please upgrade to fix it, DB is +compatible, no resynchronization required. + +Bugs fixed: + * incorrect rule depth limit for Rules witness conditions (#3810) + +## 0.108.0 "Participation" (11 Feb 2025) + +This version is compatible with the C# node 3.7.6, but also contains some +Echidna changes preview. Additional RPC extensions are introduced with this +release as well as some important fixes and improvements. + +We recommend to check your configurations wrt NeoFS synchronization options, +a new set of containers was introduced recently, old ones will eventually be +deleted (which won't break syncrhonization, but can delay it a bit). No DB +resync is required unless you want to use the new "SaveInvocations" option. + +New features: + * "Designation" event in RoleManagement native contract starting from Echidna + HF (#3761) + * base64Url encoding and decoding support in StdLib native contract starting + from Echidna HF (#3761) + * NEO candidate registration via NEP-27 payment starting from Echidna HF (#3700) + * ArchivalNode P2P capability (#3778) + * NEP-26 and NEP-27 support everywhere (#3792) + * "SaveInvocations" node parameter that allows to store more detailed + contract invocation data and retrieve it via RPC (#3569) + * "getblocknotifications" RPC API allowing to fetch filtered notifications + from all block execution contexts (#3805) + +Behavior changes: + * updated "upload-bin" command defaults (#3760) + * updated NeoFS containers for all networks (#3759) + * additional AllowNotify call flag for some NEO methods starting from Echidna + HF (#3761) + * Dump*Slot methods removed from vm.Context (#3806) + +Improvements: + * golang.org/x/crypto update from 0.26.0 to 0.31.0 (#3765) + * neotest can load contracts from NEF/manifest files now (#3771) + * more accurate memory management in persisting/processing cycles preventing + OOM conditions in most cases (#3787) + * RPC bindings now have ToStackItem and ToSCParameter methods for structures + (#3794, #3796, #3804) + * dBFT 0.3.2 with improved timers (#3799) + * github.com/consensys/gnark update from 0.11.0 to 0.12.0 (#3800) + * ability to fetch headers from NeoFS (#3789) + +Bugs fixed: + * potentially incorrect handling of misconfigured NeoFS endpoints (#3758) + * duplicate index objects are no longer an error for "upload-bin" (#3763) + * old transfer data removal problem in RemoveUntraceableBlocks configuration, + 0.107.2 regression (#3787) + * zkpbinding module producing code that can't be compiled, 0.107.0 regression + (#3802) + +## 0.107.2 "Obliteration" (13 Dec 2024) + +One more compatible patch-release that introduces `RemoveUntraceableHeaders` +application-level extension allowing to remove untraceable block headers from the DB. +This feature significantly reduces the database size, but for now it is supported in +an experimental mode, use it with care. Other than that, this release includes a fix +of BlockFetcher service that may hang on retry of NeoFS requests preventing the node +from syncing. Also, an improved algorithm of blocks uploading and extended list of +block and index file attributes are supported for `upload-bin` CLI command. + +No configuration update or DB resync is required. However, starting from this release +`NeoFSBlockFetcher` application configuration section is backed by default values for +every parameter except `Addresses` and `ContainerID`, hence if you don't like too +chatty configuration files, feel free to remove all optional parameters. + +New features: + * untraceable headers removal (#3750) + +Behavior changes: + * add `BlockTime` attribute to block objects stored in NeoFS block storage (#3749) + * use `Timestamp` attribute to hold object creation time for block and index objects + stored in NeoFS block storage (#3749) + * extended debug logs for `upload-bin` CLI command (#3751) + +Improvements: + * embed default UnitTestNet node configuration (#3696) + * NeoFS SDK dependency upgrade (#3725, #3756) + * dependent packages updates (#3746, #3747, #3748) + * refactor and speed up `upload-bin` CLI command (#3735) + * backup NeoFS BlockFetcher configuration with default values (#3742) + * reuse more of built-in NeoFS SDK functionality in `upload-bin` CLI handler (#3749) + +Bugs fixed: + * NeoFS BlockFetcher sometimes is hanging during the node startup (#3736) + * RPC server timers are improperly drained (#3737) + * basic unit test chain restore configuration (#3696) + +## 0.107.1 "Narrativization" (06 Dec 2024) + +An urgent version that fixes the problem of intensive CPU usage caused by improper +NeoFS BlockFetcher shutdown on the node's sync process completion and magnified by +additional bug at the peer discovery level. + +No configuration changes or DB resync is required. It is highly recommended to update +from 0.107.0 as soon as possible since described problems affect the speed of blocks +processing and the overall node functionality. + +Behavior changes: + * explicitly enable the list of stable hardforks in default NeoFS testnet + configuration (#3722) + +Improvements: + * decrease NeoFS storage nodes deal timeout for NeoFS BlockFetcher (#3723) + * adjust optimal number of peers for networks with small peer count (#3727) + * don't enable unstable hardforks by default (#3724) + +Bugs fixed: + * "unexpected empty payload: CMDVersion" error on peer disconnection (#3726) + * NeoFS BlockFetcher shutdown (#3728) + * connected peers count is not respected on attempt to gather more node addresses + (#3730) + +## 0.107.0 "Mongrelization" (03 Dec 2024) + +A large update that introduces a major node extension: NeoFS BlockFetcher service and +`util upload-bin` CLI command implemented as a part of [NeoFS snapshot storage +proposal](https://github.com/neo-project/neo/issues/3463). BlockFetcher service, as +an alternative to P2P synchronization mechanism, allows to download blocks and sync +chain from block dumps stored in NeoFS. Starting from this release, NeoSPCC team +maintains chain dumps in NeoFS for N3 and NeoFS public networks. The default NeoGo +node configuration for these networks has been changed to use NeoFS BlockFetcher as +a synchronization mechanism prior to P2P synchronization. Other than that, this +release includes NEP-24 standard support at both compiler and SC bindings generator +levels. A large number of tiny user-facing enhancements is rolled out for RPC +actor/ivoker, `neotest` and `unwrap` packages as far as for CLI utilities. Also, +this release contains a set of NeoGo VM bug fixes inspired by differential VM fuzzing +study conducted by our external contributor @Slava0135 and a set of tiny VM CLI +enhancements introduced by @ixje. + +Some deprecated functionality has been removed according to the schedule, see more +details in the `Behaviour changes` section. Also, for those node operators who would +like to check out our new NeoFS BlockFetcher node extension, we'd recommend to add +corresponding section to the node's configuration (see the default node configuration +for N3/NeoFS networks for example). + +This release is fully compatible with 3.7.5 version of C# node, no DB resync is +needed. + +New features: + * new NeoFS BlockFetcher service that allows to sync node from NeoFS chain dump + (#3515, #3636, #3637, #3632, #3691, #3706, #3668, #3713) + * new `util upload-bin` CLI command that allows to upload blocks from Neo chains to + NeoFS (#3578, #3625, #3626, #3633, #3637, #3638, #3643, #3650, #3662, #3684, + #3686, #3691) + * new `delete` and `ib` VM CLI commands for brealpoints management (#3674) + * NEP-24 standard support (#3560) + * Echidna hardfork introduced, but not yet enabled (please, note that this is only + a preview that includes a part of scheduled changes and will be changed in an + incompatible way before the full support, hence, this hardfork may be enabled for + experimental purpose only) (#3554) + +Behavior changes: + * neotest's AddSystemFee and TestInvoke are bound to Executor state (#3551) + * getversion RPC response is extended with seed list and standby committee (#3540) + * support only two latest versions of Go instead of three (#3567) + * some deprecated functionality is dropped: unmarshalling code for the old + `getpeers` RPC response, `NEOBalance` stackitem deserializer compatibility code, + `serv_node_version` Prometheus gauge metric, outdated RPC error codes support, + block-based web-socket transaction awaiting (#3690) + +Improvements: + * documentation updates (#3545, #3663, #3666, #3678, #3683, #3708) + * netmode package is extended with public NeoFS chain IDs (#3539) + * dBFT library upgrades (#3541, #3711) + * migration to Docker Compose V2 (#3547) + * system fee required for contract deployment in neotest is precisely calculated + (#3551) + * ability to customize awaiting options for PollingBased RPC waiter (#3556) + * Go 1.23 support, bump minimum required Go version up to Go 1.22 (#3567) + * a set of dependent libraries upgrades (#3570) + * extend the list of supported SC parameters for RPC actor/invoker (#3583) + * Null stackitem result handling for autogenerated RPC bindings (#3584) + * macos-12 support is removed, macos-14 support is added (#3657) + * allow to get NEP-11/NEP-17 balances via CLI using account address only (#3659) + * expose VM slot getters (#3677) + * add `unwrap.ErrNull` error to handle Null stackitem returned (#3695) + * extend web-socket notification subsystem with notification parameter filters + (#3689) + * explicitly prohibit unknown configuration fields for `contract generate-*` CLI + commands (#3708) + * extend compiled smart contract identifier in neotest cache (#3709) + * move `neogo_version` metric out of `network` package (#3712) + +Bugs fixed: + * fees of ditched transaction are not cleared from mempool (#3537) + * extension of SC permission descriptor doesn't clear wildcard status (#3544) + * example of NeoGo VM script fails VM execution (#3593) + * a call to `getunclaimedGas` of native Neo contract for account with zero balance + results in VM failure (#3589) + * `MODMUL` VM opcode handler returns wrong results for negative arguments (#3599) + * neotest coverage extension panics on attempt to collect coverage for contract with + missing debug information (#3600) + * `MODPOW`VM opcode handler returns wrong results for negative base (#3649) + * node panic on SIGHUP (#3661) + * HTTP return code of RPC requests diverges from C# RPC server's behaviour (#3665) + * a set of bugs in unit tests (#3442, #3680) + * `POPITEM` VM opcode handler counts stack references improperly (#3688) + * `PACKMAP` VM opcode handler ignores duplicating map keys (#3685) + * chain restore from genesis block fails with StateRootInHeader extension enabled + (#3697) + * hardfork-dependent methods are not included into native contract metadata starting + from the hardfork height (#3704) + * outdated keyword usage in Dockerfiles (#3710) + +## 0.106.3 "Lyophilization" (29 Jul 2024) + +This 3.7.5-compatible version includes a number of important fixes, so please +upgrade your nodes. Some minor extensions were also added. + +Resynchronization (or state reset) is required for testnet (because of a bug +leading to state difference since 4368840), but not required for mainnet. + +New features: + * embedded mainnet/testnet/NeoFS node configuration files (#3477, #3504) + +Behavior changes: + * CLI no longer panics if error occurs (#3495) + * MaxTraceableBlocks is 17280 now for NeoFS networks (#3518) + * minimal default RPC `SessionExpirationTime` is 5s now (#3529) + +Improvements: + * RPC actor interface extension with WaitSuccess method (#3491) + * Signers() API for RPC invokers (#3492) + * SignerAccounts() API for RPC actors (#3492) + * getpeers RPC extension with the user agent and last known block height data + (#3481) + * OnExecHook() API for VM (#3460) + * more details in witness verification error message (#3508) + * CLI help and error string format unification (#3495, #3520) + * CLI library (github.com/urfave/cli) upgrade to 2.27.2 (from v1 API, #3495) + * microoptimization of extensible sender list calculation (#3500) + * microoptimization of chain dump code (#3514) + * documentation and error messages (#3526, #3527) + +Bugs fixed: + * RPC `SessionExpirationTime` could be zero in some configurations (#3529) + * panic in WSClient unsubscription code in some multithreaded cases (#3532) + * missing PrimaryIndex in Ledger's getBlock() result (#3534) + * contract manifests with null groups were accepted (#3523) + * contract manifests with invalid features were accepted (#3523) + * contract manifests with null trusts were accepted (#3523) + * WSClient deadlock in some disconnection cases (#3535) + +## 0.106.2 "Keratinization" (13 Jun 2024) + +A 3.7.5-compatible version introducing new Domovoi hardfork that brings two fixes to +the protocol: using executing contract state to check contract call permissions +(included into this NeoGo release) and proper VM items refcounting for +System.Runtime.GetNotifications handler (not included into this NeoGo release because +we've never had this bug). Since the second bug is C#-specific and does not lead to +the state differences in mainnet/testnet, we've decided not to break the NeoGo node +to follow pre-Domovoi C# node implementation. Thus, differences in application logs +for several T5 transactions before Domovoi hardfork are expected and won't be fixed. + +Please, ensure your node configuration includes the Domovoi hardfork. No DB +resynchronisation is required. + +New features: + * Domovoi hardfork scheduled for 5570000 block of mainnet and 4144000 block of T5 + testnet (#3476, #3473, #3486, #3487) + +Behavior changes: + * hide node logs timestamp if the node is running not in TTY (#3468) + * distinguish log level for various node peer disconnection reasons (#3469) + +Improvements: + * ensure NeoFS nodes are configured when processing NeoFS oracle requests (#3455) + * NeoFS SDK dependency upgrade (#3483) + * ensure System.Runtime.GetNotifications handler can't break the MaxStackSize + constraint before and after Domovoi hardfork (#3485) + +Bugs fixed: + * deployed contract script is included into wallet's account (#3470) + * updated contract state is used to verify contract call permissions before the + Domovoi hardfork (#3473) + +## 0.106.1 "Implication" (3 Jun 2024) + +An urgent release that fixes mainnet state difference at block 5462944 which halts +blocks processing starting from the height 5468658. This release requires full node +DB resynchronization for mainnet nodes. T5 testnet DB state is not affected by this +bug (at least up to the current 4087361 height). Thus, DB resynchronisation may be +skipped for testnet nodes. No configuration changes implied. + +Bugs fixed: + * mainnet state difference at block 5462944 caused by runtime notification + permissions check against the updated contract state instead of executing state + (#3472) + * unused neofs-contract dependency in the node modules (#3458) + +## 0.106.0 "Zephyranthes" (21 May 2024) + +We're rolling out a large set of updates including all of Neo 3.7.4 protocol changes: +native contracts update functionality and extended native contract APIs united under +the upcoming Cockatrice hardfork. For smart contract developers an ability to +generate smart contract bindings with dynamic hash is supported as well as a number +of useful extensions for `unwrap` package, compatible NNS smart contract RPC binding +and a lot of other handy enhancements. This release also includes a couple of severe +regression bug fixes affecting the node state. As a bonus of fixing a wide range of +failing tests, we've refactored the node services start and shutdown procedures to +make it more stable. This version drops support for Go 1.19 and requires 1.20+ to +build (with Go 1.22 supported). + +Notice that this release requires full node resynchronization for both mainnet +and testnet nodes. Please pay a special attention to the Cockatrice hardfork schedule +and check your configurations. It should be configured for 3967000 of T5 testnet and +5450000 of mainnet. To ensure compatibility your node must be configured +appropriately. + +New features: + * native contracts update functionality bound to hardforks (#3402, #3444) + * Cockatrice hardfork planned for 5450000 block of mainnet and 3967000 block of T5 + testnet (#3402, #3448, #3402, #3446) + * `CommitteeChanged` events are emitted by NeoToken native contract starting from + Cockatrice hardfork (#3351, #3448) + * `getCommitteeAddress` method of NeoToken native contract is available starting + from Cockatrice hardfork (#3362, #3402) + * `keccak256` method of CryptoLib native contract is available starting from + Cockatrice hardfork (#3301, #3402) + * dynamic contract hash support for smart contract bindings (#3405) + * support `StringCompressed` API for `crypto.PublicKey` (#3408) + * support `Copy` API for `transaction.Transaction` and `payload.P2PNotaryRequest` + (#3407) + * extend `verifyWithECDsa` method of native CryptoLib contract with Keccak256 hasher + starting from Cockatrice and add an example of custom Koblitz-based and + Keccak256-based transaction witness verification (#3425) + * autogenerated `nativehashes` package (#3402, #3431) + * introduce `unwrap.Exception` type for better RPC invocation result exceptions + detection (#3438) + +Behavior changes: + * Neo Name Service smart contract RPC binding follows the official N3 implementation + (#3291) + * clear LastGasPerVote NeoToken account info on unvoting (#3349) + * return fault exception (if any) in `unwrap` RPC client helpers (#3356) + * move P2PNotary designation role out of P2PSigExtensions (#3452) + +Improvements: + * support `smartcontract.Convertible` interface as RPC Actor and Invoker call + parameters (#3297) + * allow to import multisignature account into wallet without WIF and password + specified (#3293) + * upgrade go-ordered-json dependency to avoid possible node state incompatibility + issues caused by smart contract manifest marshalling (#3331, #3333) + * Go 1.22 support, bump minimum required Go version up to Go 1.20 (#3336) + * a number of dependent libraries are updated to the fresh versions (#3338, #3418) + * improve error message of `System.Crypto.CheckMultisig` interop API handler (#3374) + * upgrade dBFT library to v0.2.0 (#3371, #3413) + * increase `ValidUntilBlock` value for transactions generated by CLI commands + (#3376) + * documentation updates (#3375, #3382) + * let default Notary Actor options be customizable (#3394) + * extend `getversion` RPC response with RPC server settings (#3386) + * make errors related to wallet opening more detailed (#3389) + * adjust error messages of `setExecFeeFactor` and `setStoragePrice` methods of + native PolicyContract (#3406) + * make neotest chain constructor options customizable (#3404) + * format improvements for CLI commands description (#3410) + * make state dumps comparator script more verbose (#3411) + * refactor storage `Get` implementation for BoltDB (#3441) + * add `updatecounter` field to the resulting items of `getnativecontracts` RPC API + call (#3439) + +Bugs fixed: + * node panic on committee missing from node configuration (#3294) + * autogenerated RPC bindings do not follow Go naming convention (#3299) + * a batch of failing tests is fixed (#3306, #3313, #3321, #3332, #3337, #3335, + #3344, #3340, #3350, #3355, #3364, #3368, #3377, #3393, #3397, #3392, #3398, + #3400) + * outdated minimum required Go version of boilerplate contract generated by + `neo-go contract init` (#3318) + * outdated address used as an example in RPC client documentation (#3327) + * ungraceful node services shutdown procedure (#3307) + * logging data race on node services shutdown (#3307) + * Map parameter support is missing from RPC server handlers (#3329) + * smart contract storage iterator prefix wasn't copied while iterating over values + (#3336) + * RPC error with -511 code (insufficient funds) returned on `sendrawtransaction` RPC + request should cover multiple cases of sender's insufficient funds (#3360, #3361) + * reentrancy possibility for Notary deposit withdrawal (#3357) + * null `findstorage` RPC response in case of missing storage items (#3385) + * uninitialized named return variables in compiler (#3401) + * wrong debug sequence points after `JUMP*` instructions shortening by compiler + (#3412) + * corrupted genesis block record and application log caused by improper Conflicts + attribute processing (#3437) + * false positive DB-based blocked accounts detection in native PolicyContract leaded + to invalid committee computations and reward distribution made by NeoToken (#3443) + +## 0.105.1 "Enumeration" (12 Jan 2024) + +This is another v3.6.2-compatible release that fixes mainnet state difference at +block 4688591. It is confirmed to have the same state up to 4723K height (which +is current), but to get proper mainnet state you need to resynchronize your node +from the genesis. T5 testnet state is not affected (at least up to the current +3323K height), thus DB resynchronisation may be skipped for testnet nodes. + +Improvements: + * better network services logging (#3287, #3290) + +Bugs fixed: + * state difference at block 4688591 of N3 mainnet caused by difference at + characters escaping during manifest's `Extra` field JSON serialisation (#3286) + +## 0.105.0 "Designation" (29 Dec 2023) + +We're rolling out an update for NeoGo nodes that contains a number of user-facing +API improvements for RPC web-socket notification subsystem, CLI utility, wallet +related packages and not only. Try out our new `--await` CLI option for those +commands that relay transactions to the network to automatically wait for the +on-chain transaction execution result. Subscribe for new block headers with +extended RPC notification subsystem interface. Use contract-based accounts +provided by `wallet` package and `neotest` framework to sign transactions. These +and a set of other improvements are available to our users while this release is +staying compatible with 3.6.2 version of C# node. + +This version also delivers a bug fix for consensus node panic caused by improper +native NeoToken cache initialisation. Moreover, there's a set of RPC server side +improvements, thus, we recommend to upgrade both consensus and RPC nodes to +provide more stable consensus node functioning and extended user APIs functionality. +No database resynchronisation is needed. + +New features: + * block headers RPC web-socket subscription (#3252) + * --await option to synchronize on transaction execution for CLI commands (#3265) + * partial session-based RPC iterator unwrapping (#3274) + * contract-based transaction signers in neotest framework (#3233) + * AMD64 release binaries for macOS (#3251) + * complex contract signature schemes in wallet.Account (#3256) + +Behavior changes: + * basic RPC subscription filter validity checks are implemented on both RPC + client and RPC server sides (#3258) + * filter of notary request event RPC subscription is extended with `type` field + (#3236) + * if available, use block headers RPC web-socket subscription for transaction + awaiting via `waiter` package API (#3283) + +Improvements: + * add smart contract storage limits to interop utilities (#3232) + * extend ZKP examples documentation with additional links to PoT ceremony + response files (#3234) + * support Go types in VM emitter API (#3237) + * documentation update (#3239, #3242, #3246) + * BoltDB (go.etcd.io/bbolt) dependency upgrade (#3250) + * CLI code refactoring (#2682) + * extend wallet package to work with byte slice based wallets (#3255) + * export RPC client side transaction awaiting functionality via `waiter` package + (#3265, #3283) + +Bugs fixed: + * remove stale `updatehistory` section of `getnativecontracts` RPC response (#3240) + * immediately check RPC client initialisation on access to blocks RPC subscription + API (#3257, #3261) + * fix CN panic caused by unexpected call to native NeoToken cache (#3253) + * make "automatically generated" warning of all automatically generated files + follow the standard (#3280) + +## 0.104.0 "Globalization" (27 Nov 2023) + +We're updating NeoGo to push out a number of useful updates and protocol +extensions as well as make it compatible with 3.6.2 version of C# node. The most +invasive behaviour changes are VM-level protocol constraints imposed on the size +of serialized stackitems and `NativeActivation` node setting removal. This version +also delivers a set of smaller useful changes in the interoperability layer and +native contract functionality including System.Runtime.CurrentSigners interop, +`strLen` StdLib method and PolicyContract-based transaction attributes pricing +as far as a user-facing `canceltx` CLI command and community-requested +`--relative-path` CLI option. + +Node operators must resynchronize their nodes to get fully compatible state +(which is confirmed to be compatible with 3.6.2 for current mainnet up to +block 4483627 and T5 testnet up to block 3078762). Please, ensure your node +configuration doesn't contain `NativeActivations` protocol configuration section +as this logic is hidden under Hardforks starting from the current release. + +New features: +* System.Runtime.CurrentSigners interop allowing to get signers of the currently + loaded transaction (#3058) +* `strLen` method to native StdLib contract (#3208) +* transaction attributes pricing regulation via native PolicyContract (#3155) +* `canceltx` CLI command as an alternative to unsupported `canceltransaction` RPC + request (#3223, #3214) + +Behaviour changes: +* reduce maximum allowed stackitem.Item size (#3185) +* restrict maximum allowed NEF file size and prohibit large contracts deployment (#3186) +* bind `NativeActivations` node setting to the `Hardforks` setting (#3212) +* introduce serialization limit to stackitem.Item and fail large contracts deploy + wrt this setting (#3218) +* add customizable `MaxRequestBodyBytes` and `MaxRequestHeaderBytes` RPC server + configuration setting (#3221) + +Improvements: +* provide more detailed error on attempt to read non-existent service wallet + provided via node configuration file (#3210) +* optimize emit of imported code for autogenerated RPC bindings (#3215) +* documentation update (#3203, #3222) +* add `--relative-path` CLI flag allowing to override configuration-specific relative + paths (#3206) +* update code owners (#3216, @fyrchik will live in our hearts forever) + +Bugs fixed: +* unify messages of RPC errors according to the RPC errors NEP (#3199) +* limit maximum allowed number ad depth of transaction signers and witnesses per + RPC request that accept transaction (#3207, #3221) +* forbid unknown fields usage in the node configuration file (#3209) +* enable hardfork-dependant code starting exactly from the block height specified + in the node configuration (#3211) +* require Notary native deposit to be valid for at least one subsequent block after + deposit transaction acceptance (#3211) +* deduplicate unnamed event types for autogenerated RPC bindings and make the + binding generation order strictly defined and stable (#3215, #3220) +* do not panic on trying to compile an import cycle (#3215) +* state difference at block 3002333 of T5 testnet caused by difference at characters + escaping during manifest's `Extra` field JSON serialisation (#3225) +* properly start node services that depend on native RoleManagement contract data + with genesis `Roles` protocol configuration setting specified (#3229) + +## 0.103.1 "Verification" (09 Nov 2023) + +An urgent 3.6.0-compatible version that contains a hotfix for the bug that +prevents node from starting from the existing database every new dBFT epoch. +Also, the release includes a bugfix that fails any non-zero NEO and GAS +roundtrips from accounts with zero balance. + +Although the DB format and storage states were not affected by this release, +the node resynchronization is still recommended for those who want to keep +correct application logs. Resynchronization can be skipped if you're ok with +wrong application logs for some transactions (e.g. CN doesn't care, and RPC node +cares a lot). + +Bugs fixed: +* properly initialize cache of native NeoToken contract every new dBFT epoch (#3187) +* forbid non-zero NEO and GAS roundtrips from accounts with zero balance (#3191) + +## 0.103.0 "Backwardation" (20 Oct 2023) + +A minor 3.6.0-compatible version of NeoGo with new salty ZKP-related +functionality, a large batch of deprecated APIs removal and an experimental +genesis block extension. Build Groth-16 circuits, generate proofs and deploy +verification contracts easily with the new end-to-end ZKP example and `zkpbinding` +NeoGo API. Setup node roles via the node configuration starting from the genesis +block without any painful multisignature transaction forming and invoke any +custom script in the genesis-level transaction with the new `Genesis` protocol +configuration extension. And don't forget to move from deprecated RPC client +APIs, as a lot of them have been removed. + +New node configuration format is finally adapted and the deprecated configuration +sections are permanently removed according to the schedule. Pay attention to the +`SecondsPerBlock` protocol configuration section that was replaced by +`TimePerBlock`, a set of node and services address- and port- related configuration +section that were replaced by the new `Addresses` format, direct `UnlockWallet` +consensus configuration that was replaced by a separate `Consensus` section and +a set of node-specific protocol configurations that were moved under a separate +`P2P` section. + +This release fixes a set of bugs including a vulnerability introduced by changes +in the transaction conflicts storage scheme implemented in #3061 (a part of +0.102.0 release). With the patch presented, it's impossible to uncontrollably +pollute the storage with malicious conflicting records. + +This version is fully compatible with C# node 3.6.0. However, it requires +complete resynchronization on upgrade due to the database storage scheme changes. + +New features: +* API for Groth-16 verification contracts autogeneration and end-to-end example for + proving and verifying statements on NeoGo (#3043, #3146) +* introduce genesis protocol extensions: default node roles designation and genesis + transactions (#3168) + +Behaviour changes: +* a lot of deprecated functionality is dropped: RPC client old APIs, shared + Notifications channel of WebSocket client, SecondsPerBlock protocol + configuration, old way of services and node address and port configuration, old + P2P related application settings configuration, direct UnlockWallet consensus + configuration, direct node-specific protocol configuration (#3150) +* database scheme is changed by new way of storing the transaction conflicting + records (#3138) + +Improvements: +* NeoFS SDK dependency upgrade (#3129) +* gnark and gnark-crypto dependencies upgrade (#3145, #3162, #3163) +* introduce timeout restriction for BoltDB opening (#3148) +* bump code coverage uploading action version (#3153) +* Go 1.21 support, bump minimum required Go version up to Go 1.19 (#3157) +* upgrade golang.org/x/net version (#3158) +* add protocol hardforks configuration to the `getversion` RPC response (#3160) +* format autogenerated smart contract bindings with accordance to standard `go fmt` rules (#3164) +* add "DO NOT EDIT" warning to the autogenerated smart contract bindings (#3167) + +Bugs fixed: +* avoid race between `getnextblockvalidators` RPC call handler and blockchain's + block handler routine, significantly refactor native validators caching scheme + (#3110) +* do not panic on failed NeoFS oracle requests (#3129) +* enable Notary subsystem in NeoFS mainnet configuration (#3136) +* reorganize storage scheme for transaction conflicting records to avoid possible attack (#3138) +* properly deserialize wildcard trusts field of smart contract manifest (#3140) +* use more strict GAS limit for transaction network fee calculation via RPC call (#3141) +* avoid WebSocket client request blocking (#3142) +* properly emit big uint64 constants during smart contract compilation (#3147) +* avoid race access to the node version by tests (#3149) +* make FindStorage* RPC client APIs to be always compatible with NeoC# RPC server (#3166) + +## 0.102.0 "Aberration" (06 Sep 2023) + +Long-awaited feature-packed 3.6.0-compatible version of NeoGo with all the +appropriate protocol updates and a set of tasty improvements and bug +fixes. Groth16 ZKP proof checks are there for contract developers as well as +new opcodes. A huge number of improvements went into the RPC server and +client, new RPCs, improved contract binding generator and standardized +extended error codes make developing dApps much easier. + +Users of smart contract utilities and RPC-related Prometheus metrics are +advised to take a look at the deprecated APIs removal schedule +(ROADMAP.md). This version was delayed for quite some time (waiting for 3.6), +so the next minor release (0.103.0) will remove a substantial amount of +compatibility code. It's expected to be released in November with 3.6 protocol +compatibility (3.7 cycle is likely to require more time). + +This is also the first version that drops support for Go 1.17 and requires +1.18+ to build (with Go 1.20 supported). Using generics in smart contracts is +not supported yet, but some elements of this support can be implemented in +future versions. + +Mainnet and testnet node operators, please pay attention to the Basilisk +hardfork schedule and check your configurations. It will happen at block +2680000 for T5 testnet and 4120000 for mainnet. To ensure compatibility your +node must be configured appropriately. This version requires DB +resynchronization, so schedule your updates accordingly. + +New features: + * ZKP proof checking support via CryptoLib native contract (operations with + BLS12-381 curve points) (#2940, #3042) + * System.Storage.Find interop now support "Backwards" flag to iterate in the + opposite direction (#2952) + * `util ops` CLI utility that pretty-prints VM script (#2965) + * `notarypool_unsorted_tx` Prometheus metric for notary-enabled nodes (#2696) + * `CloseNotificationChannelIfFull` WSClient option allowing the client to + close notification channel on overflow (#2988) + * autogenerated RPC bindings for contract events and conversion code from + stackitem.Item to structure (#3008, #3035, #3036, #3087) + * event type inference from contract code (#3008) + * dynamic contract hashes support for RPC bindings (#3012) + * "Basilisk" protocol hardfork support (#3056, #3080, #3086, #3104) + * ABORTMSG and ASSERTMSG VM opcodes (#3066) + * standardized RPC error codes (#3063) + * `findstorage` and `findstoragehistoric` RPC support (#3099) + * `getstoragehistoric` RPC support (#3099) + * `getrawnotarypool` and `getrawnotarytransaction` RPC (#3098) + +Behaviour changes: + * deprecated `FromAddress` smart contract helper is dropped from the `util` + interop package (#2941) + * a number of deprecated RPC-related Prometheus counters are permanently + removed (#2941) + * NEP-2 account label will be asked on account import via CLI (#2889) + * hashes and states of native contracts are now accessible via native + ContractManagement API (#2991) + * `serv_node_version` gauge Prometheus metric is marked as deprecated and + replaced by `neogo_version` and `server_id` (#3009) + * strict contract script check is back with Basilisk hardfork, it was + previously removed in 0.101.3 for 3.5 protocol compatibility (#3056) + * JSON number deserialization (via StdLib.jsonDeserialize) changes with + Basilisk hardfork allowing for more precision and handling more corner + cases (#3080) + * notification type errors are treated as fatal after Basilisk hardfork, + before it they're just logged (#3086) + * NULL and non-UTF8 items are not allowed for String event types (#3086) + * `Conflicts` and `NotValidBefore` transaction attributes are no longer NeoGo + extensions, they can be used on regular networks (#2962) + +Improvements: + * documentation and example improvements (#2945, #2972, #2979, #3020, #3084, + #3099, #3114, #3116, #3119, #3121) + * `getproof` and `verifyproof` RPC API support in RPC client (#2942) + * Go 1.20 support, bump minimum required Go version up to Go 1.18 (#2908) + * faster state reset process (#2819) + * optimized voting data storage scheme for NEO contract (#2892, #2893) + * NeoFS SDK dependency upgrades (#2995, #3032) + * special exported error returned in case of WSClient disconnection (#3000) + * automatic guessing of contract and manifest filenames for `contract + compile` CLI command and `loadnef` VM CLI command (#3013) + * support for pushing stackitem.Convertible objects via VM script emitter (#3016) + * economic adjustment for ranking of transactions with `Conflicts` attribute (#3031) + * `google.golang.org/grpc` dependency upgrade fixing high severity security + vulnerability (#3055, gRPC is only used to communicate with NeoFS nodes in + the oracle service) + * enforce default RPC server values when it's used as an independent package + (not a part of node) (#3107) + * unwrap.Nothing function for RPC clients (#3117) + * address and reverse hash display in opcode dumps (#3115) + +Bugs fixed: + * invalid peer port type returned by `getpeers` RPC response (#2914) + * invalid data source for `mempool_unsorted_tx` Prometheus metric (#2969) + * dBFT library upgrade fixing the ability of a single node to speed up the + process of new blocks creation for the whole network (#3018, + nspcc-dev/dbft#75) + * failing CALLT instructions in VM CLI for loaded NEF files (#3020) + * missing signers check for on-chain conflicting transactions (#3061) + * incorrect sequence point boundaries in debug data (#3074) + * compiler panic on encountering generic code (#3041) + * potential pooled fallback notary transaction changes (#3108) + * lost LastGasPerVote value on NEO state deserialization for non-voting + accounts (#3122) + +## 0.101.4 "Yarborough" (01 Aug 2023) + +Another one 3.5.0-compatible version that is aimed to fix T5 testnet state +difference that has happened at block 2336911 which leads to inability to process +new blocks since 2418703. The issue is fixed by allowing JSON numbers +unmarshalling from scientific notation to Integer stackitem. Maximum parsing +precision for such numbers is currently restricted by 53 bits. This is a +temporary C#-compatible solution that is likely to change in the future versions +when an appropriate C# node bug is fixed (neo-project/neo#2879). + +A set of minor bug fixes is included as well to flush some of the long-awaited +changes that were blocked by the 0.102.0 release delay (caused by v3.6.0 C# node +release delay). In particular, invalid headers returned by an RPC server for +error responses, invalid format of incremental dumps created by CLI and deadlock +on unhealthy RPC server shutdown. Long-awaited `--config-file` CLI option to +start the node providing a single configuration file is added. + +T5 testnet chain requires a complete resynchronization for this version. Mainnet +chain resynchronization is recommended, but not required. + +New features: + * `--config-file` CLI option allowing to start the node with a single configuration file (#3014) + +Improvements: + * blockchain Notary and Oracle services documentation improvement (#2972) + * BoltDB (`go.etcd.io/bbolt`) dependency upgrade that fixes a number of Windows-related issues (#3034) + +Bugs fixed: + * panic on node start with invalid configuration (#2968) + * deadlock on unhealthy RPC server shutdown (#2966) + * improper WSClient notification channels managing after disconnection (#2980) + * missing Prometheus metric initialisation on node start (#2992) + * invalid initialisation of native contracts cache (#2994) + * incorrect way of incremental DB dumps creation (#3047) + * Notary contract is allowed to be a sender of main Notary request transaction (#3065) + * discrepancy in signer's witness scope parsing on the RPC server side (#3060) + * Invoker calling API isn't allowed to accept nil parameter (#3067) + * contract RPC Client unwrapper helper can't handle missing contract case (#3072) + * JSON numbers can't be unmarshalled to stackitem from scientific notation (#3073) + * invalid content-type header returned by RPC server on error responses (#3075) + +## 0.101.3 "Yuckiness" (08 Jul 2023) + +Yet another 3.5.0-compatible emergency version that removes scrupulous +instructions check of smart contract's script on contract deployment or update. +Presence of this check leads to the known T5 testnet state incompatibility +(since 1670095) which causes inability to process new blocks (since 2272533). +It should be noted that the corresponding check was accidentally removed from +the reference C# node implementation way back in neo-project/neo#2266 and added +again in neo-project/neo#2849 which is planned to be a part of the upcoming +3.6.0 C# node release. Thus, changes made in the presented 0.101.3 release will +be reverted afterwards and strict contract script check will be present in the +next 3.6.0-compatible version of NeoGo node. + +T5 testnet chain requires a complete resynchronization for this version. Mainnet +chain resynchronization is recommended. + +Bugs fixed: + + * extra strict contract script check on contract deployment or update is + removed (#3052) + +## 0.101.2 "Excavation" (29 Jun 2023) + +One more (and unexpected one!) 3.5.0-compatible version that fixes a VM bug +leading to mainnet state incompatibility (since 3672783) which in turn leads +to inability to process new blocks (since 3682290). + +Mainnet chain needs to be resynchronized for this version. + +Improvements: + * documentation updates (#3029, #2990, #2981) + +Bugs fixed: + * incorrect handling of empty Any-type parameter for RPC invocations (#2959) + * incorrect state rollbacks in case of exception during cross-contract call + when the call is made from non-TRYing context (#3046) + +## 0.101.1 "Shallowness" (17 Mar 2023) + +Another 3.5.0-compatible version that delivers important bug fixes and +provides a new API to be used by NeoFS. An upgrade is recommended, the DB +doesn't need to be resynchronized. + +New features: + * internal RPC client for deeply integrated applications like NeoFS (#2916) + +Improvements: + * documentation updates (#2879, #2880, #2893, #2917, #2920, #2936) + * code style, spelling and updated linter fixes (#2884, #2922, #2933) + * NEP-2 import password can be provided via config file now (#2887) + * custom stack item deserialization limit is available via public APIs now (#2904) + * RPC client endpoint can be retrieved via public API (#2915) + * dependency updates (#2919, #2929) + * WSClient now copies filter parameters to Subscribe* and Receive* methods + improving code safety (#2937) + +Bugs fixed: + * name parameter ignored for wallet import command (#2887) + * incorrect RPC binding code generated for Any return type (#2918) + * memory leak on active peer disconnection (#2924) + * consensus process deadlock (#2930) + * dBFT deadlock in "committed at previous view" scenario (#2935) + * panic in RPC waiter code (#2938) + +## 0.101.0 "Shortness" (13 Jan 2023) + +This release delivers an important fix for block execution application logs +and requires a resynchronization, therefore it's 0.101.0 (even though it's +also 3.5.0-compatible). It fixes some other minor problems as well (the other +most notable change probably is in the compiler), so we recommend upgrading. + +Improvements: + * updated golang.org/x/* dependencies (#2854) + * CLI help and required flags handling fixes (#2852) + * transfer data storage optimization (#2865) + * network's magic number is stored (and checked against the config on + startup) in the DB now, reducing potential for node operator errors (#2867) + +Bugs fixed: + * in rare cases nodes could request an invalid number of blocks from peers + leading to disconnect (#2846) + * outdated documentation fixes (#2860, #2876) + * application logs for blocks that contained GAS spends for transaction fees + contained (and returned from getapplicationlog RPC) incorrect (off by one) + values for amount in Transfer events; transaction application logs were not + affected by this, but data returned to RPC event subscribers could + potentially be (#2865) + * findstates RPC returned an error instead of an empty data set for valid + contracts that have no data (unlike C# node, #2866) + * miscompiled shadowed range loop variable definition (#2871) + * missing (compared to C# node) explicit (even though null) 'exception' field + in the getapplicationlog RPC output from server (#2872) + +## 0.100.1 "Chaptalization" (28 Dec 2022) + +This is a tiny update that 99.99% of users can easily skip. The reason for +this release is the need to fix compatibility with the NeoFS mainnet +sidechain and have some stable version to be used there. In any other case it +can be ignored, but if you still decide to upgrade you don't need to +resynchronize. + +Behaviour changes: + * Aspidochelone fork is made to include ContractManagement native contract + deploy/update methods call flags change, initially it was an unconditional + part of 0.99.0 NeoGo release (or 3.2.0 C# version), but this behavior is + incompatible with the NeoFS mainnet sidechain; the change to the fork logic + does not affect any other public networks (mainnet/testnet) and any new + networks that have Aspidochelone enabled since block 0 (#2848) + +Improvements: + * more robust NEP-11 divisibility check in the RPC server code (#2841) + * microoptimization for some debug log messages (#2842) + * additional fuzz tests for integer serialization and script parsing code (#2851) + +## 0.100.0 "Centuplication" (08 Dec 2022) + +A 3.5.0-compatible version of NeoGo with all the appropriate protocol updates +and a number of other changes. The most notable ones are configuration +updates. New features and some long-standing inconsistencies required for some +changes and this release brings them with it. Old configurations are still +supported and will work the same way (except for one minor exception in +VerifyBlocks that is only supposed to be used for tests/development), but we'd +like to highlight that all of the old settings will be removed in ~6 months, +so please review these updates and update your configurations. For public +networks the best way to go is to take the new versions from the "config" +directory and then adjust for particular scenario if needed. + +This release requires a complete resynchronization due to native contract +changes, so please schedule your updates appropriately. + +New features: + * System.Runtime.LoadScript syscall (and appropriate smart contract interops) + allowing to load/run dynamic code (#2719) + * PUSHT/PUSHF VM instructions allowing for simpler/cheaper booleans (#2770) + * ContractManagement native contract was extended with ID->hash mappings and + `getContractById` and `getContractHashes` methods (#2702, #2837) + * LogLevel application configuration option that can be changed on SIGHUP + (#2831) + * additional type data generated by the compiler that then can be used by the + RPC bindings (SDK/contract wrapper) generator, this allows for complex + types (structures/arrays/maps) to be easily represented/handled in + contract-specific RPC code (#2828) + * multiaddress listeners for all services and P2P, this changes the old + Address/Port (and AnnouncedPort for P2P) configuration scheme to more + generic Addresses list, the old configuration is still supported, but is + deprecated and will be removed in future node versions (#2827, #2839) + +Behaviour changes: + * Aspidochelone fork block for NeoFS sidechain mainnet configuration is + rescheduled again (#2823, #2830) + * Blockchain's GetHeaderHash() method now accepts uint32 for parameter + (#2814) + * pre-0.97.3 and pre-0.99.0 deprecated compatibility fields and logic were + dropped from the result.Version (`getversion` RPC result) structure (#2786) + * SecondsPerBlock protocol configuration variable was replaced with + TimePerBlock allowing for sub-second precision, SecondsPerBlock is still + supported, but will eventually be removed (#2829) + * AttemptConnPeers, BroadcastFactor, DialTimeout, ExtensiblePoolSize, + MaxPeers, MinPeers, PingInterval, PingTimeout, ProtoTickInterval settings + were moved into the new P2P section, timing parameters now use Duration + type allowing for more human-friendly values in many cases, old parameters + are still supported, but will eventually be removed (#2827) + * consensus (dBFT) is configured as a separate service now that can be + enabled/disabled (and can work in "watch only" mode without a wallet), the + old direct "UnlockWallet" specification in "ApplicationConfiguration" is + still supported, but is deprecated and will be removed in future versions + (#2832) + * GarbageCollectionPeriod, KeepOnlyLatestState, RemoveUntraceableBlocks, + SaveStorageBatch and VerifyBlocks settings were moved from + ProtocolConfiguration to ApplicationConfiguration; old configurations are + still supported, except for VerifyBlocks which is replaced by + SkipBlockVerification with inverted meaning (and hence an inverted default) + for security reasons; this also affects NewBlockchain and neotest APIs + (#2833) + +Improvements: + * more user-friendly error and help messages in some cases (#2824, #2834) + * faster node startup time and smaller memory footprint for networks with + lots (1-2M+) of blocks (#2814) + * minor documentation fixes (#2834, #2838) + +Bugs fixed: + * transactions with system fee or more than MaxBlockSystemFee are no longer + accepted into the mempool, preventing a form a network-wide DoS (#2826) + * deprecated WSClient subscription methods not working correctly when filters + are being used (#2836) + +## 0.99.7 "Hyalinization" (23 Nov 2022) + +Bugs, terrestrial arthropods with at least six legs. Sometimes we don't notice +them, sometimes we ignore them, but they do exist and there are times when +they need to be dealt with in one way or another. Most of the bugs fixed in +this release have a little less legs than proper insects, they're far from +being as critical as the ones fixed in the previous release and yet we +consider them annoying enough to warrant this new version of NeoGo to be +published. We don't want to see our users fighting these pesky creatures in +the wild, better update and have a troublefree experience. + +If you're not affected by #2815 (requests for verbose transaction data and +application logs failed for some specific mainnet transaction(s)) you may +leave the DB as is with this upgrade, if you want it to be solved then please +resynchronize. + +Behaviour changes: + * "loadtx" VM CLI command now uses transaction system fee value as the + default GAS limit for the context, unless --gas is used (previously it + wasn't limited, #2816) + * SendRawTransaction RPC client method will now always return transaction + hash, even if error occured, this also affects Actor APIs and allows to + handle some specific errors (like transaction already present in the + mempool) in more appropriate way (#2817) + * Actor and NotaryActor Wait() wrapper methods now handle "already exists" + and "already on chain" errors as non-errors, waiting (if needed) and + returning proper results (#2817) + +Improvements: + * reworked network discoverer/connection manager that has a better picture of + the network (especially if it's a small one), doesn't try to make additional + connections (even when having less than MinPeers of them) when there are no + more real addresses to connect to, establishes stable connections faster in + containerized environments and does more connection attempts when MinPeers + setting is not satisfied, but there are potential candidates (#2811) + * VM invocation stack handling microoptimization (#2812) + * load* VM CLI commands now accept --gas parameter to set the amount of GAS + to be used in this context (#2816) + * better logging during state reset process (#2813) + * subscription-based transaction waiter now handles already accepted + transactions faster (~immediately vs waiting for block timeout previously, + #2817) + * WSClient can return more specific error on websocket handshake failure if + it's provided by server (#2821) + * new MaxWebSocketClients configuration option for the RPC server to control + the number of allowed websocket clients, missing or 0 value make the server + use the default of 64 which is compatible with the previous behavior (you + can use old configurations if you don't care about this option, #2821) + +Bugs fixed: + * occasional "loadgo" VM CLI test failures because of timeout (#2810) + * websocket waiter test instability (#2809) + * websocket event unsubscriptions could lead to node deadlock in rare + circumstances (#2809) + * exception handler could use improper evaluation stack in some cases (#2812) + * pointer stack items were not handled correctly in "protected" serialization + mode leading to inability to deserialize these items back (#2816) + * state reset functionality not working for big chains like mainnet (#2813) + +## 0.99.6 "Funambulation" (16 Nov 2022) + +An emergency release fixing mainnet incompatibility at block 2504320 leading +to inability to process block 2504813. A couple of other less severe bugs got +fixed as well. Unfortunately, this release requires complete resynchronization +for mainnet, other networks can use old DBs. + +Improvements: + * only transactions requested by the consensus process are forwarded to it + now slightly improving performance in some networked scenarios (#2788) + * a complete set of simple array unwrappers in the unwrap library (#2792) + * RPC binding generator can handle simple arrays now with configuration file + provided (#2792) + * more user-friendly error messages in some cases (#2797, #2802, #2804) + +Bugs fixed: + * DB was not properly closed after state reset (#2791) + * VM stack handling problem leading to lost items after return from call to + another contract (#2800) + * "istack" command panic in the VM CLI (#2800) + * failure to decode incorrect contract from ContractManagement state leading + to failure to start the node, it'll be ignored now similar to how C# node + treats this (#2802) + * subscription to execution events failed if no "state" filter was used along + with "container" (#2804) + * Actor using WSClient could deadlock in the transaction waiter during + unsubscription (#2804) + +## 0.99.5 "Underestimation" (11 Nov 2022) + +It wasn't long since the previous release until a juicy set of features and +optimisations came on board. So we've decided to change our release plan and +supply one more 3.4.0 compatible version. It comes up with a massive suite +of network P2P and broadcast improvements. We've carefully investigated message +sending timeouts on benchmarks, reworked the broadcast logic and significantly +reduced delays in message sending and the amount of useless traffic between our +nodes. For smart-contract developers and RPC client users several new features +were added. These are automatic RPC bindings generator, poll-based and +websocket-based transactions awaiting and an extended websocket client +subscriptions interface. For better debugging experience we've improved state +related functionality of VM CLI and added an ability to reset chain state to a +particular height. + +An important dBFT initialization bug that caused timestamp-related errors on +block addition was fixed. Also, those who use NeoGo node with +EnableCORSWorkaround setting may want to update the node to be able to handle +web-socket requests with Origin set in the header. The users of websocket RPC +client subscription mechanism are welcomed to move from deprecated subscriptions +API to the extended one. Finally, we are kindly asking our users to have a look +at our roadmap file where deprecated code removal schedule is attached. + +This version is fully compatible with C# node 3.4.0 and does not require +resynchronization on upgrade. This time for sure, the next release is planned +to be compatible with 3.5.0. + +New features: + * logarithmic gossip fan-out, new `BroadcastFactor` application setting and + adaptive peer requests are added to optimise broadcasting process (#608, + #2678, #2743) + * Prometheus histograms with handling time are added for P2P commands (#2750) + and RPC calls (#2784) + * transaction awaiting is added to RPC clients (#2740, #2749) + * RPC bindings generator (#2705) is added for both safe (#2766) and + state-changing (#2769, #2778) methods with initial iterator support (#2783) + * the ability to reset chain state to a particular height is added (#2576, + #2773) + +Behaviour changes: + * Aspidochelone fork block for NeoFS sidechain mainnet configuration is + rescheduled (#2754, #2776) + * RPC server will now handle any Origin in websocket connection when + EnableCORSWorkaround setting is on (#2772, #2724) + * WS RPC client subscriptions API is extended to accept custom subscriber + channels, old subscriptions API is marked as deprecated (#2756, #2764) + +Improvements: + * network code refactoring (#2742) + * network packets sending is now limited in time (#2738) + * broadcast messages will be sent concurrently to each peer (#2741) + * ping messages are broadcast using the common broadcast logic (#2742) + * transactions requested by dBFT will be accepted on demand only (#2746, #2748, + nspcc-dev/dbft#63) + * VM CLI's state-based functionality improvements (extended signers and + arguments parsing, enabled logging) and additional commands (`loadtx`, + `loaddeployed`, `jump`) (#2729, #2606, #2740) + * incoming P2P transactions are now processed concurrently and buffer for + inventory hashes is reusable (#2755) + * `GetData` and `GetBlocksByIndex` replies are now batched on a TCP-level + (#2757) + * optimisation of incoming P2P transactions and blocks filtering are introduced + (#2758, #2759) + * batch size of broadcast transactions hashes is adjusted (#2760) + * peer reconnections logic is improved, `GetAddr` messages are sent more + often (#2761, #2745) + * documentation for wallet configuration file is clarified (#2763) + * Koblitz curve implementation of btcd/btcec was replaced with decred/secp256k1 + due to GitHub security warning (#2781) +* `NOT` opcode for Bool emission is now used to reduce resulting script + cost (#2762) + +Bugs fixed: + * failing tests cleanup on Windows (#2736) + * race in CLI test (#2765) + * invalid conversion of PublicKey smart contract parameter to emitable (#2739) + * initialize dBFT's context with previous block's timestamp on view 0 (#2753, + #2752, nspcc-dev/dbft#64) + +## 0.99.4 "Transliteration" (07 Oct 2022) + +A small update mostly interesting for people building/testing smart contracts +with NeoGo. It contains long-awaited VM CLI update that allows to use +blockchain state and complete set of interops, additional helper functions for +smart contracts and notification checking code (for upcoming 3.5.0 protocol +changes). Node operators using EnableCORSWorkaround (that is still not +recommended, but available) also may want to update to be able to handle +pre-flight CORS requests. + +This version is compatible with C# node 3.4.0 and does not require +resynchronization on upgrade. The next release is planned to be compatible +with 3.5.0. + +New features: + * address conversion helpers for smart contracts (#2698) + * interop helper to call specific version of a contract (#2695) + * notifications are now checked for manifest compliance, a warning is logged + in case on detected inconsistencies (#2710) + * VM CLI can now use blockchain state DB (including historic states) to run + code with a complete set of interops and contracts available; additional + commands were added to it to inspect storage and generated events (#2723) + +Behavior changes: + * type assertion with two return values in Go contracts can't be compiled now + (it never worked properly, #2718) + * RPC server will now handle pre-flight CORS requests (via OPTIONS methods) + when EnableCORSWorkaround setting is on (#2724) + * all transaction-generating CLI commands were unified to accept + gas/sysgas/out/force parameters, ask for confirmation and be able to save + transaction into a file; this affects wallet claim and candidate-related + commands mostly (#2731) + +Improvements: + * smartcontract.Builder API was extended with Len method (#2691) + * NNS example contract adjustments for better DNS compatibility (#2679) + * documentation updates (#2708, #2722, #2726, #2728) + * compiler now emits code to explicitly cast runtime.Notify() parameters to + appropriate types (#2720) + * CLI tests repackaging (#2725) + * NeoFS sidechain configurations for mainnet and testnet (#2730) + +Bugs fixed: + * panic on node shutdown (#2689) + * panic on inlined append() compilation (#2716) + +## 0.99.3 "Simplification" (09 Sep 2022) + +Version 0.99.3 brings with it most of the planned refactoring changes to the +RPC client. dApp backend code can be greatly simplified in many cases by +using new APIs (old ones are still available for transition period of about +one-two releases more). We also have some updates for CLI and compiler and a +number of bug fixes. + +This version is compatible with C# node 3.4.0 and does not require +resynchronization on upgrade. The next release is planned to be compatible +with 3.5.0 (if it's to be released around currently planned dates). + +New features: + * native contract RPC wrappers (#2643, #2646, #2650, #2666, #2665) + * NEP-11 RPC wrappers (#2651) + * invoker interface extension with session-based iterators support (#2650) + * notary Actor greatly simplifying creation of notary-assisted transactions + via RPC client (#2665) + * historic smart contract calls can now be made via CLI (#2683) + +Behavior changes: + * calculatenetworkfee RPC can handle paid attributes (NeoGo extensions) and + invalid contract signatures now, it won't return an error for them (#2658) + * graceful node shutdown on SIGTERM (#2660) + * wallet balance commands now require at least 0.99.1 NeoGo version (or + compatible C# node) used by the RPC server (#2667) + +Improvements: + * build system corrections (#2641, #2684) + * additional types in `unwrap` (#2650, #2651) + * session iterator consistency check in `unwrap` (#2650) + * multitransfers in NEP-17 RPC wrapper (#2653) + * extended transaction validity time in CLI wallet commands (#2653) + * reference counter optimization in VM (#2659) + * RPC Actor API can have default (used for all transactions) attributes and + callbacks now (#2665) + * neptoken RPC package can now provide wallet.Token data (#2667) + * NEP-11 balance commands can now work without direct token hash + specification or previous token import (#2667) + * support for offline signing in CLI (#2668) + * compiler can optimize out unused global variables now (#2624) + * private keys are now cleaned up from memory when they're no longer needed, + additional Account APIs added to reduce direct interactions with private + keys (#2672) + * updated linter settings, some code cleanup (#2674) + * more documentation and examples for new RPC APIs (#2681) + * refactored state-changing methods of NEP-11/NEP-17 RPC wrappers into a + separate structure to simplify reusing them in higher-level code (#2681) + * simplified rpcclient historic API (#2686) + +Bugs fixed: + * compiler panic on empty package list (#2645) + * compiler not allowed to use unnamed parameters in exported methods (#2648) + * compiler allowed to export multireturn functions (#2648) + * compiler panic on nil method receiver in the compiled code (#2649) + * compiler panic on variable initialization from multireturn call (#2644) + * potential lockups or panics on node shutdown (#2652) + * contract manifest not checked for correctness in bindings generation CLI + command (#2656) + * SignTx wallet Account API could lead to inconsistent result in some cases + (#2665) + * wallet Account API allowed to sign with locked accounts (#2665) + * potential panic in keys.WIFDecode on some inputs (#2672) + +## 0.99.2 "Recalibration" (12 Aug 2022) + +This is a 3.4.0-compatible (tested for mainnet and testnet) update to NeoGo +that implements all of the required protocol changes, adds a lot of new RPC +client interfaces and provides additional service management functions. This +is also the first version to support the recently released Go 1.19. Support +for Go 1.16 is removed at the same time, so you need Go 1.17+ to build NeoGo +now. + +With this version you can turn on/off, restart or reconfigure internal node +services like RPC or Oracle without full node shutdown/startup. Node operators +can send an appropriate Unix signal and that'll do the job. At the same time +node operators must resynchronize their nodes when updating to 0.99.2 due to +native contract state change. + +A number of RPC client's methods are now deprecated (but still available to +simplify transition), please explore new APIs, try them and leave feedback in +#2597 if any. We expect new APIs to better fit the needs of various backend +RPC client use cases while allowing to remove some repetitive code from +applications at the same time. This release only contains generic NEP-17 call +wrapper, so it doesn't cover all of the things currently provided by the +old client API, but this will be fixed in future releases. + +New features: + * signal-based configuration and service reloads (#2612) + * new APIs in smartcontract package to convert Go types into NEP-14 + parameters (#2621, #2632) + * invoker package to deal with invocations via RPC in various contexts + (mostly useful for read-only and/or historic calls when used directly, + #2621) + * `hasMethod` method in the ContractManagement native contract (#2598, #2619, + #2640) + * actor package that helps in creating and sending transactions to the + network via RPC using the same set of signers (can be used directly, but + mostly intended to be used by higher-order contract wrappers, #2632, #2637) + * unwrap package in the RPC client that checks for execution results and + converts returned stack items to expected Go types (can be used directly, + but more useful when being used by upper-layer contract wrappers, #2638) + * nep17 RPC client package for simple remote access to NEP-17 contract + methods via RPC, both safe and state-changing (#2642) + +Behavior changes: + * smartcontract.Params type and (*Parameter).TryParse APIs were removed + completely as unused and not useful (#2621) + * compiler now rejects unnamed parameter for exported methods (but they + didn't work properly anyway, #2601) + * restored deploy-time out of bounds script checks (#2538, #2619) + * dynamic scripts (like entry scripts) no longer can emit notifications + (#2613) + * db dump and db restore CLI commands now reject `--debug` option (it was + ignored previously, #2625) + +Improvements: + * manifest correctness check during compilation (#2601) + * more robust CalledByEntry scope check (#2613) + * a check for excessive arguments in CLI (#2625) + * better help messages for CLI commands (#2625) + * Go 1.19 support (#2634) + * saved transactions created via CLI (`--out`) now use the maximum possible + ValidUntilBlock values (#2632) + * RPC server now returns more data about NeoGo-specific protocol extensions + in `getversion` reply (#2632) + +Bugs fixed: + * NEP-6 wallet version was wrong wrt the standard and C# implementation + (#2633) + * smart contract invocations from the CLI didn't compensate for interactive + prompt wait time which could lead to transaction expiration before it was + sent (#2632) + * Oracle native contract's `finish` method reentrancy problem (#2639) + +## 0.99.1 "Parametrization" (28 Jul 2022) + +We're updating NeoGo to push out a number of significant updates as well as +make it compatible with 3.3.1 version of C# node. The most prominent changes +are RPC sessions for iterators returned from invoke* calls, initial bits of +RPC client refactoring and support for darwin-arm64 and linux-arm64 +platforms. A number of internal changes are less visible from outside, but +are also important for future evolution of the code base. + +Node operators must resynchronize their nodes to get fully compatible state +(which is confirmed to be compatible with 3.3.1 for current mainnet up to +block 1932677 and T5 testnet up to block 475938). RPC client users, please +review the changes carefully and update your code wrt refactorings made as +well as iterator session support. + +Subsequent releases will also change RPC client and associated code, we want +to make it easier to use as well as extend its functionality (see #2597 for +details, comments and suggestions are welcome). 0.99.2 is expected to be +released somewhere in August with 3.4.0 C# node compatibility. + +New features: + * `getcandidates` RPC method to get full list of registered candidates and + their voting status (#2587) + * wallet configuration file support to simplify using CLI non-interactively, + use `--wallet-config` option instead of `--wallet` if needed (#2559) + * session-based JSON-RPC iterator API for both server and client with + `traverseiterator` and `terminatesession` calls; notice that the default + server behavior (`SessionEnabled: false`) is compatible with NeoGo 0.99.0, + iterators are expanded the way they were previously, only when sessions are + enabled they're returned by the server to the client (#2555) + * `interop` package now provides helper methods for proper `Hash160`, + `Hash256` and other type comparisons in smart contracts (#2591) + * `smartcontract.Builder` type to assist with entry script creation as well + as a set of CreateXXXScript functions for simple cases (#2610) + +Behavior changes: + * 3.3.1-compatible order of ABI methods for the native NeoToken contract + which affects LedgerContract state (#2539) + * `getnextvalidators` RPC was reworked to be compatible with C# node, if you + rely on Active flag from the old response or full candidate list please use + `getcandidates` method (#2587) + * `--account` parameter for `contract manifest add-group` CLI command was + changed to `--address` (short `-a` is still the same) for better + consistency with other commands (#2559) + * (*WSClient).GetError won't return an error in case the connection was + closed with (*WSClient).Close + * `getnepXXbalances` RPCs now return decimals, symbol and token name data + along with asset hash (#2581) + * Ledger.GetBlock will now return state root hash for chains that use + StateRootInHeader extension (#2583) + * `request.RawParams` type is gone (but it likely wasn't used anway), if + needed use `[]interface{}` directly (#2585) + * `RawParams` field of `request.Raw` was renamed to `Params` (#2585) + * `vm.State` type moved to a package of its own (`vm/vmstate`) to avoid + importing whole VM where only State type is needed (#2586) + * `MaxStorageKeyLen` and `MaxStorageKeyLen` definitions moved from + `core/storage` to `config/limits` package simplifying their usage (#2586) + * `vm.InvocationTree` type moved into `vm/invocations` (#2586) + * `storage.Operation` type moved into `storage/dboper` package (#2586) + * `rpc/server` package moved to `services/rpcsrv` (#2609) + * `rpc/client` package moved to `rpcclient` (#2609) + * `network/metrics` package moved to `services/metrics` (#2609) + * `rpc/request` and `rpc/response` packages were merged under `neorpc`, + `request.Raw` is `neorpc.Request` now, while `response.Raw` is + `neorpc.Response` (#2609) + * `rpc.Config` type was moved to `config.RPC` (#2609) + * `subscriptions.NotificationEvent` type moved to + `state.ContainedNotificationEvent` (#2609) + * `subscriptions.NotaryRequestEvent` type moved to + `result.NotaryRequestEvent` (#2609) + +Improvements: + * new `make` targets to build NeoGo binaries locally or using Docker + environment (#2537, #2541) + * more detailed client-side error on JSON-RPC WebSocket connection closure + (#2540) + * various node's micro-optimizations both for CPU usage and memory + allocations (#2543) + * new Wallet method that allows to save it in pretty format (#2549) + * massive test code refactoring along with some core packages restructuring + (#2548) + * transaction package fuzz-test (#2553) + * simplified client-side Error structure, more predefined errors for + comparisons, refactored server side of RPC error handling (#2544) + * more effective entry scripts for parameterless function invocations via RPC + (#2558) + * internal services now can start/stop properly independent of other node + functionality (#2566, #2580) + * all configurations now use 'localhost' instead of 127.0.0.1 (#2577) + * documented JSON behavior for enumerations which deviates from C# node + slightly (#2579) + * better error messages in the CLI for invocations (#2574) + * compiler can inline methods now (#2583) + * `interface{}()` conversions are supported by the compiler now (although + they don't change values, #2583) + * server-side code moved out of common RPC packages completely (#2585, #2586) + * metrics and DB configurations moved to config and dbconfig packages (#2586) + * calling methods on returned values is now possible in Go smart contracts + (#2593) + * NNS contract now cleans up old entries in case a domain is registered again + after expiration (#2599) + * GetMPTData P2P messages (from P2PStateExchangeExtensions) can now be + processed even if KeepOnlyLatestState is enabled (#2600) + * builds and regular tests for MacOS (#2602, #2608) + * internal `blockchainer.Blockchainer` is finally gone, simplifying some + dependencies (#2609) + * updated CLI `--version` output format to conform with NeoFS tooling style + (#2614) + +Bugs fixed: + * dBFT update: increase the number of nodes that respond to RecoveryRequest + (#2546, nspcc-dev/dbft#59) + * invalid block height estimation for historic calls (#2556) + * Signature parameter to RPC invocations was expected in hex format instead + of base64 (#2558) + * missing data in RPC server error logs in some cases (#2560) + * potential deadlock in consensus node before the dBFT process is started + (#2567) + * dBFT update: ChangeView messages were not correctly transmitted in + RecoveryResponse messages (#2569, nspcc-dev/dbft#60) + * dBFT update: ChangeView messages from newer views were not correctly + processed by outdated node (#2573, nspcc-dev/dbft#61) + * RPC server restarts via SIGHUP could lead to server not starting at all in + very rare case (#2582) + * inlined code couldn't have multiple `return` statements (#2594) + * empty list of transactions is now returned instead of `null` in `getblock` + RPC responses when block doesn't contain transactions, the same way C# node + does (#2607) + * candidate registration didn't invalidate committee cache leading to state + differences for T5 testnet (#2615) + +## 0.99.0 "Overextrapolation" (03 Jun 2022) + +A big NeoGo upgrade that is made to be compatible with C# node version +3.3.0. All of the protocol changes are implemented there with the main one +being the Aspidochelone hardfork that will happen on mainnet +soon. Compatibility is confirmed for current T5 testnet and mainnet, but this +version requires a resynchronization so schedule your updates accordingly. + +But it's not just about the protocol changes, this release introduces an +ability to perform historic invocations via RPC for nodes that store old MPT +data. Using `invokefunctionhistoric` you can perform some invocation with the +chain state at the given height, retreiving old balances, ownership data or +anything else you might be interested in. + +Please also pay attention to configuration files used by your node, mainnet +ones must have Aspidochelone hardfork enabled at height 1730000 to be +compatible and T5 testnet enables it at height 210000. T5 testnet is different +from T4, it uses different magic number, different seed nodes and different +protocol configuration settings. 0.99.0 won't work for T4 testnet, please +use 0.98.5 for it. + +New features: + * new methods in native contracts: + - getTransactionVMState and getTransactionSigners in LedgerContract (#2417, + #2447, #2511) + - murmur32 in CryptoLib (#2417) + - getAllCandidates and getCandidateVote in NeoToken (#2465) + * System.Runtime.GetAddressVersion syscall (#2443) + * StartWhenSynchronized option for RPC server (defaults to the old behaviour, + #2445) + * historic RPC invocations (invokefunctionhistoric, invokescripthistoric and + invokecontractverifyhistoric APIs, #2431) + * protocol hardforks, with Aspidochelone being the first supported (#2469, + #2519, #2530, #2535) + * MODMUL and MODPOW VM instructions (#2474) + * ability to get connection closure error from WSClient (#2510) + * isolated contract calls with state rollback on exception (#2508) + * vote and candidate state change events in the NEO contract (#2523) + * immutable compound types in VM (#2525) + +Behavior changes: + * ContractManagement deploy and update methods now require AllowCall flag + (#2402) + * GetStorageItems* APIs are no longer available in dao package, use Seek* + (#2414) + * committee candidates are now checked against the Policy contract block list + (#2453) + * getCandidates NEO method returns no more than 256 results now (#2465) + * EQUAL checks are limited to 64K of data in VM now irrespective of the + number of elements compared (#2467) + * Create[Standard/Multisig]Account prices were raised to avoid DoS attacks + (#2469) + * System.Runtime.GetNotifications syscall costs 16 times more GAS now (#2513) + * System.Runtime.GetRandom syscall now costs more and uses more secure seed + (#2519) + +Improvements: + * better messages for some CLI commands (#2411, #2405, #2455) + * fixes and extensions for example contracts (#2408) + * better neotest documentation (#2408) + * internal tests now use neotest framework more extensively (#2393) + * neotest can now be used for benchmark code and has more multi-validator + chain methods (#2393) + * big (>64 bit) integers can now be used for RPC calls (#2413) + * no error is logged now when notary-assisted transaction is already in the + mempool (it's not an error, #2426) + * T5 testnet with more aggressive protocol parameters (#2457) + * destroyed contracts are blocked now (#2462) + * JSONization errors for invoke* RPCs are now returned in exception field of + the answer (#2461) + * typos, grammar and spelling fixes in documentation, comments and messages + (#2441, #2442) + * faster RPC client initialization (#2468) + * RPC processing errors use ERROR log severity now only if there is a + server-side error occurred (2484) + * increased server-side websocket message limit to fit any request (#2507) + * invalid PrepareRequest now doesn't require other nodes to be alive to send + ChangeView (#2512) + * updated YAML library dependency (#2527) + * notary subsystem compatibility fixes, using new IDs and options (#2380) + * minor performance improvements (#2531) + +Bugs fixed: + * websocket-based RPCs were not counted in Prometheus metrics (#2404) + * input data escaping missing for RPC log messages (#2404) + * compiler panic in notification checking code (#2408) + * missing 'hash' field in the debug data (#2427) + * debug data used relative paths that are not compatible with neo-debugger + (#2427) + * getversion RPC method wasn't compatible with C# implementation (#2435, + #2448) + * old BaseExecFee and StoragePrice values could be used by transactions in + the same block with transactions that change any of them (#2432) + * context initialization race in dbft (#2439) + * some stateroot data functions used incorrect keys in the DB (#2446) + * voter reward data could not be deleted in NEO contract in some cases + (#2454) + * the maximum number of HTTPS oracle redirections is limited to 2 (and only + using HTTPS) for C# compatibility (#2456, #2389) + * maximum number of contract updates wasn't limited leading to overflow (#2462) + * incorrect interop signature for getCandidates NEO contract method (#2465) + * next instruction validitiy check is performed now before instruction + pointer move to be compatible with C# implementation (#2475) + * concurrent map access in Seek leading to panic (#2495, #2499) + * insecure password reads (#2480) + * minor VM reference counting fixes (#2498, #2502, #2525) + * panic during serialization of transaction with empty script (#2485) + * 'exception' field was missing in the invoke* RPC call output when there is + no exception which differed from C# node behaviour (#2514) + * interop interfaces used incompatible (wrt C# node) type string in JSON + (#2515) + * minor genesis block state differences wrt C# implementation (#2532) + * incompatible (wrt C#) method offset check (#2532) + +## 0.98.5 "Neutralization" (13 May 2022) + +An urgent update to fix the same security issue that was fixed in 3.1.0.1 C# +node release. Please upgrade as soon as possible, resynchronization is not +needed for mainnet. + +Bugs fixed: + * GAS emission now happens after NEO transfer is finished (#2488) + +## 0.98.4 "Mesmerization" (11 May 2022) + +An urgent release to fix incompatibility with mainnet at block 1528989. The +actual pair of problems leading to inability to process this block occurred +earilier than that, so to fix this you need to resynchronize your node. Fixed +node is confirmed to have identical state as 3.1.0 C# node up to block +1529810. + +Bugs fixed: + * StdLib itoa() implementation emitted uppercase letters instead of lowercase + (#2478) + * StdLib jsonDeserialize() implementation couldn't handle properly integers + larger than 64-bit signed (#2478) + +## 0.98.3 "Liquidation" (07 May 2022) + +This is a hotfix release to fix t4 testnet incompatibility at block +1589202. The actual problem was found and fixed during 0.99.0 development +cycle, but 0.99.0 is expected to be incompatible with t4 testnet. This release +allows to continue working with it as well as mainnet (and contains some other +fixes for known problems). It does not require resynchronizing a node. + +Improvements: + * double call to `WSClient.Close()` method won't cause a panic (#2420) + +Bugs fixed: + * Rules scope considered as invalid in binary representation (#2452) + * incorrect compressed P2P message could lead to panic (#2409) + * notary-assisted transaction could be in inconsistent state on the Notary + node (#2424) + * WSClient panics if request is made after connection breakage (#2450) + * Rules scope JSON representation wasn't compatible with C# implementation + (#2466) + * JSONized Rules scope could only contain 15 conditions instead of 16 (#2466) + +## 0.98.2 "Karstification" (21 Mar 2022) + +We've decided to release one more 3.1.0-compatible version bringing all of the +new features and bug fixes (along with complete support for Windows platform) +made before going into full 3.2.0 compatibility. It's important for us to give +you more stable and flexible environment for the ongoing hackathon. Contract +bindings generator might be very useful for anyone operating with already +deployed contracts written in other languages, while the node itself can now +be configured for lightweight operation, keeping only the data relevant for +the past MaxTraceableBlocks, no more and no less. Current public networks +don't benefit from it yet with their large MaxTraceableBlocks setting, but +they're growing every day and it's just a matter of time when this feature +will start making a difference. And sorry, but this release is again faster +than the previous one. + +We recommend updating, but if your node is not public and you don't +specifically want to play with new things brought by it you might as well wait +for the next one. It requires resynchronization and 0.99.0 will do too because +of planned protocol changes. + +New features: + * protocol extension allowing to change the number of validators in existing + network (#2334) + * MPT garbage collection mechanism allowing to store a set of latest MPTs, + RemoveUntraceableBlocks setting now activates it by default unless + KeepOnlyLatestState is used (#2354, #2360) + * NEP-11/NEP-17 transfer data garbage collection (#2363) + * Go bindings generator based on contract manifests (#2349, #2359) + +Behavior changes: + * some RPC client functions for divisible NEP-11 changed signatures using + slice of bytes now instead of strings for IDs (#2351) + * heavily refactored storage and dao package interfaces (#2364, #2366) + * support for Go 1.18 added, 1.15 dropped (#2369) + +Improvements: + * additional CLI tests (#2341) + * `defer` statement is now compiled more effectively (#2345) + * `defer` statement can be used in conditional branches now (#2348) + * new tests for divisible NEP-11 contract (#2351) + * all tests failing on Windows platform are fixed (#2353) + * RPC functions now accept integers larger than 2^64 from strings (#2356) + * 10-35%% improvement in bulk block processing speed (node synchronization) + depending on machine and configuration (#2364) + * reworked VM CLI fixing Windows incompatibility and UTF-8 bugs (#2365) + * incompletely signed transaction JSONs now also contain hash (#2368) + * RPC clients can now safely be used from multiple threads (#2367) + * additional tests for node startup sequences (#2370) + * customizable notary service fee for Notary extension (#2378) + * signers passed into RPC methods can now contain rules (#2384) + * compiler tests now take less time (#2382) + * some fuzzing tests added (#2399) + +Bugs fixed: + * test execution result wasn't checked for CLI invocations that saved + transaction into file (#2341) + * exception stack wasn't properly cleared during CALL processing in VM (#2348) + * incorrect contract used in RPC client's GetOraclePrice (#2378) + * oracle service could be tricked by redirects into going to local hosts when + this was disabled (#2383) + * `invoke*` RPC functions could be used to trigger OOM with specially crafted + scripts (#2386) + * some edge-cased integer conversions were not exactly matching the behavior + of C# node in VM (#2391) + * HASKEY instruction wasn't working for byte arrays (#2391) + * specially-crafterd JSONPath filters for oracle requests could trigger OOM + (#2372) + * ENDFINALLY opcode before ENDTRY could lead to VM panic (#2396) + * IsScriptCorrect function could panic on specially-crafted inputs (#2397) + +## 0.98.1 "Immunization" (31 Jan 2022) + +Bug fixes, interesting optimizations, divisible NEP-11 example and a big +compiler update --- everything you wanted to find in this NeoGo update. It +requires chain resynchronization, but this resynchronization will be faster +than ever. + +One thing should also be noted, even though this release is 3.1.0-compatible, +it is known to have a different state for testnet after block 975644, but it's +not a NeoGo fault, it'll be fixed in the next C# node release. The root cause +is well-known and is not considered to be critical compatibility-wise. + +New features: + * support for reading the wallet from stdin in CLI (where it's possible, #2304) + * new CLI command for wallet password change (#2327) + * `getstateroot` support in RPC client (#2328) + * divisible NEP-11 example (#2333) + * helper script to compare node states via RPC (#2339) + +Behavior changes: + * zero balance is explicitly printed now for token-specific NEP-17 balance + requests from CLI (#2315) + * pkg/interop (used by smart contracts) is a separate Go module now (#2292, + #2338) + * smart contracts must be proper Go packages now (#2292, #2326) + +Improvements: + * optimized application log storage (#2305) + * additional APIs in `neotest` framework (#2298, #2299) + * CALLT instruction is now used by the compiler where appropriate leading to + more optimized code (#2306) + * DB seek improvements increasing chain processing speed by ~27% (#2316, #2330) + * updated NeoFS dependencies (#2324) + * optimized emitting zero-length arrays in `emit` package used to construct + scripts (#2299) + * native contract tests refactored using generic contract testing framework (#2299) + * refactored internal Blockchainer interfaces, eliminating unnecessary + dependencies and standardizing internal service behavior (#2323) + * consensus process now always receives incoming transactions which might be + helpful for accepting conflicting (wrt local pool) transactions or when the + memory pool is full (#2323) + * eliminated queued block networked re-requests (#2329) + * better error reporting and parameter handling in FindStates RPC client + method (#2328) + * invoke* RPCs now also return notifications generated during execution (#2331) + * it's possible to get storage changes from the result of invoke* RPCs (#2331) + * better transfer data storage scheme resulting in faster getnep* RPC + processing (#2330) + * dropped deprecated `loader` package from compiler dependencies moving to + updated x/tools interface (#2292) + +Bugs fixed: + * incorrect handling of missing user response in CLI (#2308) + * improper primary node GAS distribution in notary-enabled networks (#2311) + * excessive trailing spaces in the VM CLI output (#2314) + * difference in JSON escaping wrt C# node leading to state difference for + testnet at block 864762 (#2321) + * potential panic during node shutdown in the middle of state jump (#2325) + * wrong parameters for `findstates` call in RPC client (#2328) + * improper call flags in Notary contract `withdraw` method (#2332) + * incorrect ownerOf signature for divisible NEP-11 contracts (#2333) + * missing safeness check for overloaded methods in the compiler (#2333) + +## 0.98.0 "Zincification" (03 Dec 2021) + +We've implemented all of Neo 3.1.0 protocol changes in this release (and +features like NEP-11 transfer tracker), but as usual it's not the only thing +it contains. If you're developing contracts in Go you might be interested in +contract testing framework this release brings with it, which allows you to +write automated tests in Go. You can also build a node for Windows now and +most of functionality works there (but some known problems still exist, so +this port is considered to be experimental for now). As usual there were some +optimizations made in various components as well as bug fixes. + +There are also some things removed in this release, that is BadgerDB and +RedisDB support, this will allow us to optimize better for LevelDB and BoltDB +in future. Notice also that this release requires full node resynchronization. + +New features: + * optional signer scope parameter for deploy command (#2213) + * interop packages extended with Abort() function and useful constants (#2228) + * notary subsystem now allows to have multiple multisignature signers as well + as combining simple notary-completed accounts with multisignature ones (#2225) + * configurable method overloading for the compiler (#2217) + * initial support for the Windows platform (#2239, #2265, #2267, #2264, + #2283, #2288) + * contract testing framework (#2229, #2262) + * `Rules` witness scope (#2251) + * PACKMAP/PACKSTRUCT opcodes and UNPACK extension (#2252) + * NEP-11 transfer tracking with RPC API (#2266) + * support for structure field slicing in the compiler (#2280) + * invoked contract tracing for invoke* RPCs (#2270, #2291) + +Behavior changes: + * BadgerDB and RedisDB are no longer available for DB backend (#2196) + * GetNEP17Transfers() RPC client method now works with Uint160 parameter + instead of address (#2266) + +Improvements: + * compiler optimizations (#2205, #2230, #2232, #2252, #2256) + * output fees as proper decimals in the CLI (#2214) + * private network created from Makefile now uses dynamic IP addresses (#2224) + * various minor optimizations across whole codebase (#2193, #2243, #2287, #2290) + * `util convert` command now also handles base64 representations of script + hashes (#2237) + * unified "unknown transaction" RPC responses (#2247) + * faster state switch when using P2P state synchronization extension (#2201) + * better logging for oracle errors (#2275) + * updated NeoFS client library dependency (#2276) + +Bugs fixed: + * getproof RPC API didn't handle destroyed contracts correctly (#2215) + * invokefunction RPC returned error if no parameters were passed (#2220) + * incorrect handling of empty MPT batch (#2235) + * incoming block queue using more memory than it should, up to OOM condition + on stuck node (#2238) + * panic on peer disconnection in rare cases (#2240) + * panic on password read failure (#2244) + * miscompiled functions using `defer` and returning no values (#2255) + * oracle responses processed not using request witnesses (#2261) + * CreateTxFromScript() RPC client method incorrectly handled CustomContracts + and CreateTxFromScript sender scopes (#2272) + * potential OOM during synchronization on systems with slow disks (#2271) + * incorrect oracle peer configuration in the default private network (#2278) + * compiler error processing functions using defer and inlined calls (#2282) + * node that is simultaneously a CN and Oracle node could create invalid + oracle response transactions (#2279) + * incorrect GetInvocationCounter() result in some cases (#2270) + +## 0.97.3 "Exception" (13 Oct 2021) + +This updated version of NeoGo is made to be compatible with Neo 3.0.3 bringing +with it all corresponding protocol changes and bug fixes so that you can use +it on public testnet and mainnet. It also brings with it a complete +experimental P2P state exchange extension for private networks as well as +additional optimizations. This is the first version requiring at least Go 1.15 +to compile, so make sure your Go is up to date if you're not using pre-built +binaries. Protocol updates and bug fixes made in this release require a +resynchronization on upgrade. + +We also make a final call on Badger and Redis DB issue #2130, if there are no +active users of them, support for both will be removed in the next release. + +New features: + * MaxConnsPerHost RPC client option (#2151) + * P2P state exchange extension (#2019) + * transaction-related CLI commands now show calculated fees before + sending transaction to RPC node and ask for confirmation (#2180) + * test invocations are now possible for partially-signed transactions stored + in JSON format (used for manual signing by multiple parties, #2179) + * `getstate` and `findstates` RPC to retrieve historic state data (#2207) + +Behavior changes: + * added support for Go 1.17, dropped Go 1.14 (#2135, #2147) + * blocking native contracts is no longer possible in Policy contract (#2155) + * `getversion` RPC call now also returns protocol configuration data (#2161, + #2202) + * NEF files now have Source field that can be specified in contract's + metadata (#2191) + * notification events from contracts in subscription service now also contain + container (transaction usually) hash (#2192) + * out of bounds exceptions can be catched in contracts now for PICKITEM and + SETITEM VM instructions (#2209) + +Improvements: + * reduced number of memory allocations in some places (#2136, #2141) + * VM optimizations (#2140, #2148) + * notary subsystem documentation (#2139) + * documentation fixes (#2162) + * VM CLI now allows to dump slots (#2164) + * better IPv6 checks in example NNS contract and `getAllRecords` method (#2166) + * updated linters (#2177) + * Migrate methods renamed into Update in examples (#2183) + * old (no longer available) interop function names removed (#2185) + * optimized processing of voting NEO transfers (#2186) + * configuration parameters specified in seconds no longer use (improper) + time.Duration type (#2194) + * better error message for conflicting transactions (#2199) + * open wallet in read-only mode if not changing it (#2184) + * optimized header requests in P2P communication (#2200) + * compiler now checks for method existence if it's specified as safe in + metadata (#2206) + +Bugs fixed: + * incorrect CustomGroups witness scope check (#2142) + * empty leaf values were ignored for MPT calculcations (#2143) + * lower-case hexadecimal in block header JSON output (differing from C# node, + #2165) + * `getblockheader` RPC result missing `Nonce` and `Primary` fields (#2165) + * return empty list of unverified transactions for `getrawmempool` RPC + instead of null (C# node never returns null, #2165) + * return empty list of NEF tokens in JSON format instead of null which C# + node never returns (#2165) + * races in some tests (#2173) + * parameter context JSON serialization/deserialization incompatiblity with C# + node leading to interoperability problems (#2171) + * transfers of 0 GAS or NEO were not possible (#2169) + * incorrect ContentTypeNotSupported oracle response transaction handling (#2178) + * JSON escaping differences with C# implementation (#2174) + * NEO balance update didn't save LastUpdatedBlock before GAS distribution + leading to problems in transactions with recursive NEO transfers (#2181) + * panic in CLI transfer command when missing destination address (#2211) + * multiple blank identifiers in function/method were misinterpreted by the + compiler (#2204) + * `getnep17transfers` used uint32 for internal time processing which is not + enough for ms-precision N3 timestamps (#2212) + * PICKITEM instruction could fail on attempt to get an item with long key + from map (#2209) + +## 0.97.2 "Dissipation" (18 Aug 2021) + +We're rolling out an update for NeoGo nodes that mostly concentrates on +performance. We've tweaked and tuned a lot of code while staying compatible +with N3 mainnet and testnet. At the same time we're gradually introducing +changes required for our P2P state exchange extension and this affected DB +format, so you'll need to resynchronize on update. This update is not +mandatory though, 0.97.1 is still perfectly valid for N3 networks. + +Note also that we're discussing removal of Badger and Redis databases support +in future releases, so if you're interested in them take a look at #2130. + +Behavior changes: + * address blocking in Policy contract now also blocks calls to contracts with + blocked addresses (#2132) + +Improvements: + * numerous memory and CPU optimizations across whole codebase (#2108, #2112, + #2117, #2118, #2122, #2123, #2128, #2114, #2133) + * preliminary work for P2P state exchange extension (#2119) + * `util convert` command now also detects public keys and converts them to + script hash/address (#2125) + +Bugs fixed: + * key decoding functions could accept some additional data even though only + key is expected to be present (#2125) + * conflicting dummy transactions could stay in the DB even with block removal + enabled (#2134) + +## 0.97.1 "Gasification" (06 Aug 2021) + +We're updating NeoGo to make it compatible with the latest protocol changes +made in 3.0.2 version of C# node. But that's not the only thing we do, this +release also fixes one important bug, improves node's performance and adds CLI +support to add group signatures to manifests. + +It requires resynchronization on upgrade. + +New features: + * `contract manifest add-group` command to add group signatures to contract + manifest (#2100) + +Behavior changes: + * GAS contract no longer has Refuel method, it could be used as DOS + amplification tool for attacks on network and there is no way to securely + fix it (#2111) + +Improvements: + * memory store optimizations leading to substantial single-node TPS + improvements (#2102) + * various micro-optimizations across the board both for CPU usage and memory + allocations (#2106, #2113) + * optimized transaction decoding (#2110) + +Bugs fixed: + * ping messages created with wrong value used for node's height (#2115) + +## 0.97.0 "Ventilation" (02 Aug 2021) + +This is an official 3.0.0-compatible release that is ready to be used both for +mainnet and testnet. Technically, 0.96.0 and 0.96.1 are compatible too, but +they need an updated configuration to work on mainnet while this version has +it covered. + +We keep improving our node and this release is not just a repackage of +something older, so DB format changes require a resynchronization if you're +upgrading from 0.96.X. + +Behavior changes: + * updated configuration for mainnet (#2103) + +Improvements: + * documentation for contract configuration file (#2097) + * significant change to NEP-17 tracking code, it shouldn't affect valid + NEP-17 tokens, but now we store a little less data in the DB and get more + from token contracts when needed (for `getnep17balances` RPC for + example); this change is required for our future state exchange protocol + extension (#2093) + * improved block processing speed on multicore systems (#2101) + * JSON deserialization now has the same limits as binary, but this doesn't + affect any valid code (#2105) + +Bugs fixed: + * potential deadlocks in notary-enabled nodes (#2064) + * wallet files not truncated properly on key removal (#2099) + +## 0.96.1 "Brecciation" (23 Jul 2021) + +New CLI commands, updated dependencies and some bugs fixed --- you can find +all of this in the new NeoGo release. It's compatible with 0.96.0 (except for +multisignature contexts, but you're not likely to be using them) and +confirmed to have proper RC4 testnet state up to 15K blocks (but 0.96.0 is +fine wrt this too). At the same time we recommend to resynchronize the chain +if you're using LevelDB or BoltDB, both databases were updated. + +New features: + * `query candidates`, `query committee` and `query height` CLI commands + (#2090) + * `GetStateHeight` RPC client call support (#2090) + +Behavior changes: + * `wallet candidate getstate` command was renamed to `query voter`, now it + also prints the key voted for and handles addresses with 0 balance without + spitting errors (#2090) + +Improvements: + * `query tx` now outputs signer address along with script hash (#2082) + * updated many of node's dependencies including BoltDB and LevelDB, there are + no radical changes there, mostly just some fixes improving stability (#2087) + * better error messages in some places (#2089, #2091) + +Bugs fixed: + * `query tx` command wasn't providing correct results if used with C# RPC + node (#2082) + * watch-only node (with a key, but not elected to participating in consensus) + could panic on receiving a number of Commit messages (#2083) + * `getstateheight` RPC call produced result in different format from C# node + (#2090) + * JSON representation of multisignature signing context wasn't compatible + with C# implementation (#2092) + +## 0.96.0 "Aspiration" (21 Jul 2021) + +We're updating NeoGo to support RC4 changes, there are some incompatible ones +so this version can't be used with RC3 networks/chains (RC3 testnet is +still available, please use 0.95.4 for it). This release was checked for RC4 +testnet compatibility and has the same state as C# for the first 5K blocks. + +No significant protocol changes are expected now as we're moving to final N3 +release, but we'll keep updating the node fixing bugs (if any), improving +performance and introducing NeoGo-specific features. + +New features: + * "System.Runtime.GetNetwork" system call (#2043) + * ContentTypeNotSupported oracle return code and content type configuration + for Oracle service (#2042) + * block header have "Nonce" field again which is used for + "System.Runtime.GetRandom" system call (#2066) + * import from incremental chain dumps (#2061) + * configurable initial (from the genesis block) GAS supply (#2078) + * "query tx" CLI command to check for transaction status (#2070) + +Behavior changes: + * verification GAS limits were increased (#2055) + * SQRT price reduced (#2071) + * binary deserialization is now limited to 2048 (MaxStackSize) items (#2071) + * notary deposit is stored now as serialized stack item (#2071) + * testnet and mainnet configuration updates (#2077, #2079, #2081) + +Improvements: + * faster stack item binary and JSON serialization (#2053) + * faster Storage.Find operation (#2057) + * additional documentation for public network CNs (#2068) + * better code reuse for native contract's data serialization (#2071, see new + stackitem.Convertible interface) + * some types from state package renamed to remove "State" word (#2075) + * util.ArrayReverse helper moved to package of its own with additional + helpers (#2075) + +Bugs fixed: + * nested structure cloning could lead to OOM (#2054, #2071) + * addresses were not accepted for witness accounts in RPC calls (#2072) + * POW instruction could take unknown amount of time to complete for big + parameters (#2060) + * oracle contract could accept invalid user data (#2071) + * EQUAL for deeply nested structures could never complete (#2071) + * arrays and structures were limited to 1024 items (#2071) + * oracle fallback transactions could try using outdated ValidUntilBlock value + (#2074) + * oracle node starting from genesis block tried to process all requests from + the chain instead of only working with current ones (#2074) + * some tests used non-unique/wrong temporary files and directories (#2076) + +## 0.95.4 "Yatter" (09 Jul 2021) + +Making a fully compliant Neo node is not easy, there are lots of minuscule +details that could be done in a little different way where both ways are +technically correct but at the same time just different which is enough to +create some interoperability problems. We've seen that with Legacy node +implementation where we were functionally complete with 0.74.0 release but +some fixes kept coming up to the most recent 0.78.3 version. It was a bit +easier with Legacy because we already had some years-long chains to test the +node against while N3 is a completely new territory. + +So we'd like to thank all RC3 hackathon participants as well as all other +people playing with N3 RC3 network, your joint efforts gave enough material +to keep us busy fixing things for some time. The chain moving forward hit more +and more edge cases with every block and this is actually very helpful, we saw +a lot of things that could be improved in our node and we improved them. + +Now we're releasing 0.95.4 with all of these fixes which is still +RC3-compatible and it's stateroot-compatible with C# implementation up to 281K +blocks. Please resynchronize to get identic testnet state. And most likely +that's the last RC3-compatible release as we're moving forward to the official +3.0.0 release of C# version NeoGo will be updated with appropriate changes +(some of which are not compatible with RC3). + +New features: + * 'sysgas' parameter for invocation and transfer CLI commands to manually add + some system fee GAS (#2033) + +Behavior changes: + * contract calls are now checked against specified permissions by the + compiler (#2025) + * stack items with nesting levels of 10 and more won't be serialized to JSON + for purposes like including them into RPC call reply (#2045) + +Improvements: + * AddHeaders() with multiple headers added at the same time now works with + StateRootInHeader option (#2028) + * cached GAS per vote value leads to substantially improved block processing + speed (#2032) + * failed native NEP-17 transfers now still re-save the old value to the + storage making state dumps compatible with C# (#2034) + * refactored and renamed some stackitem package functions, added proper error + values in all cases (#2045) + * RPC calls (and application logs) can now return real serialization errors + if there are any (previously all errors were reported as "recursive + reference", #2045) + * better tests (#2039, #2050) + +Bugs fixed: + * wrong onNEP11Payment name in contract configuration examples (#2023) + * missing permission sections in contract configuration examples (#2023) + * buffer stack item deserialization created byte string (#2026) + * Extra contract manifest field serialization was reworked to match C# + implementation more closely (#2021) + * wildcard permission in manifest allowed any methods, while it shouldn't + (#2030) + * group permission in manifest worked incorrectly for a set of groups (#2030) + * call flags were JSONized in incompatible way (#2041) + * MPT update left branch nodes with 1 child in some cases (#2034, #2047) + * JSON marshalling code for stack items detected item recursion in some cases + where no actual recursion was present (#2045) + * binary serialization code for stack items could overwrite old errors in + some cases (#2045) + * binary serialization code for stack items wasn't ensuring maximum size + limit during serialization which could lead to OOM (#2045) + * batched MPT update create excessive extension nodes for the last child of + branch node in some cases (#2047) + * in some cases "TCP accept error" was logged during node shutdown (#2050) + * contract updating code was updating caches before they should've been + updated (#2050) + * INVERT/ABS/NEGATE unary operations were not copying their arguments (#2052) + +## 0.95.3 "Yuppification" (17 Jun 2021) + +One more N3 RC3-compatible release that fixes testnet state difference at +block 151376. Please resynchronize to get proper testnet state. + +Behavior changes: + * NEP2-related functions in `crypto/keys` package changed a bit to allow + Scrypt parameters overriding, standard parameters are available via + `NEP2ScryptParams` function (#2001) + +Improvements: + * better unit test stability (#2011, #2001) + * updated neofs-api-go dependency (with support for TLS-enabled NeoFS node + connections, #2003) + * removed annoying token matching warning (#2018) + +Bugs fixed: + * state mismatch resulting from different committee candidate sorting (#2017) + +## 0.95.2 "Echolocation" (10 Jun 2021) + +This is another N3 RC3-compatible release and it's better in its RC3 +compatiblity than the previous one because we've fixed some state mismatches +wrt C# implementation that were found on testnet. It is confirmed to have the +same state up to 126K height (which is current), but to get proper state you +need to resynchronize your node from the genesis. + +New features: + * RPC notification subsystem was extended to support notary pool events, so + clients can now react on request additions (#1984) + * compiler now checks notification event name length to fit into the limit + (#1989) + * compiler now also checks for runtime.Notify() calls from Verify method + (which won't work anyway, #1995) + +Improvements: + * codegeneration improvements removing some unnecessary store/loads and type + conversions (#1881, #1879) + * additional MPT tests added ensuring compatibility with C# implementation + (#1993) + * additional consistency check added to prevent node running with native + contracts differing from the ones saved in DB (#2010) + +Bugs fixed: + * `calculatenetworkfee` RPC result used format different from C# node (#1998) + * `CALLT` opcode was missing any price leading to wrong GAS calculations and + different state wrt C# node (#2004) + * '+' character was emitted directly by `jsonSerialize` method which is fine + wrt JSON itself, but differs from C# node behavior leading to node state + difference (#2006) + * NEO self-transfers were not checking the amount transferred (they didn't + change balance, but they succeeded) leading to state difference wrt C# + implementation (#2007) + +## 0.95.1 "Shiftiness" (31 May 2021) + +Bringing NeoGo up to date with N3 RC3 changes this release also improves +compiler and CLI a bit. + +This release is mostly compatible with 0.95.0, but you need to resynchronize +your chains to have proper stateroot data and to make new methods available to +contracts. At the same time there won't be long-term support provided for it, +just as with all previous previews and RC. + +New features: + * base58CheckEncode/base58CheckDecode methods in StdLib native contract + (#1977, #1979) + * getAccountState method in NeoToken native contract (#1978, #1986) + * custom contracts and groups can now be specified for witness scopes in + invocations from CLI (#1973) + +Improvements: + * local variable handling was refactored in the compiler, removing some + duplicate code and improving robustness (#1921) + * CLI help now describes how "unvote" can be done (#1985) + +Bugs fixed: + * boolean parameters to function invocations via RPC were not processed + correctly (#1976) + * VM CLI used too restrictive default call flags for loaded scripts (#1981) + * IPv6 check in NNS contract was out of date wrt C# implementation (#1969) + +## 0.95.0 "Sharpness" (17 May 2021) + +This version mostly implements N3 RC2 protocol changes (and is fully +RC2-compatible), but also brings NEP-11 CLI support and small improvements for +node operators. + +Please note that this release is incompatible with 0.94.1 and there won't be +long-term support provided for it. + +New features: + * CLI command for NEP-17 transfers now accepts `data` parameter for the + transfer (#1906) + * contract deployment CLI comand now also accepts `data` parameter for + `_deploy` method (#1907) + * NEP-11 and NEP-17 transfers from CLI can now have multiple signers (#1914) + * `System.Runtime.BurnGas` interop was added to burn some GAS as well as + `refuel` GAS contract method to add GAS to current execution environment + (#1937) + * port number announced via P2P can now differ from actual port node is bound + to if new option is used (#1942) + * CLI now supports full set of NEP-11 commands, including balance and + transfers (#1918) + * string split, memory search and compare functions added to stdlib (#1943) + * MaxValidUntilBlockIncrement can now be configured (#1963) + +Behavior changes: + * `data` parameter is now passed in a different way to NEP-17 RPC client + methods (#1906) + * default (used if nothing else specified) signer scope is now + `CalledByEntry` in CLI and RPC (#1909) + * `SignAndPushInvocationTx` RPC client method now adds as many signatures as + it can with the wallet given which in some cases allows CLI to create + complete transaction without going through multisignature procedure (#1912) + * `getversion` RPC call now returns network magic number in `network` field + (#1927) + * RoleManagement native contract now emits `Designation` event in + `designateAsRole` method (#1938) + * `System.Storage.Find` syscall now strips full prefix given when + `FindRemovePrefix` option is used (#1941) + * LT, LE, GT, GE VM opcodes now accept Null parameters (#1939) + * `features` field was re-added to contract manifests, though it's not + currently used (#1944) + * node will reread TLS certificates (if any configured) on SIGHUP (#1945) + * contract trusts are now expressed with permission descriptors in manifest + (#1946) + * NEP-11 transfers now also support `data` parameter (#1950) + * N3 RC2 testnet magic differs from N2 RC1 testnet (#1951, #1954) + * stdlib encoding/decoding methods now only accept inputs no longer than 1024 + bytes (#1943) + * `System.Iterator.Create` interop was removed with all associated logic (#1947) + * `Neo.Crypto.CheckSig` and `Neo.Crypto.CheckMultisig` interops were renamed + to `System.Crypto.CheckSig` and `System.Crypto.CheckMultisig` (#1956) + * oracle requests now use Neo-specific JSONPath implementation (#1916) + * native NNS contract was removed and replaced by non-native version (#1965) + +Improvements: + * RPC errors reported by server are now more verbose for `submitblock`, + `sendrawtransaction` calls (#1899) + * all CLI commands that accept addresses now also accept hashes and vice + versa (#1905) + * code cleanup based on a number of static analysis tools (#1908, #1958) + * CLI implementation refactoring (#1905, #1911, #1914, #1915, #1918) + * only one state validator now sends complete stateroot message normally + (#1953) + * oracle HTTPS requests are now sent with User-Agent header (#1955) + * stdlib `itoa` and `atoi` methods can now be called with one parameter + (#1943) + * oracle nodes are no longer on extensible payload whitelist (#1948) + * extensible message pool is now per-sender with configurable size (#1948) + * `static-variables` field support for debugger as well as debug data for + `init` and `_deploy` functions (#1957) + * user documentation for configuration options (#1968) + +Bugs fixed: + * `getproof` RPC request returned successful results in some cases where it + should fail + * `Transfer` events with invalid numbers were not rejected by NEP-17 tracking + code (#1902) + * boolean function parameters were not accepted by `invokefunction` RPC call + implementation (#1920) + * potential races in state validation service (#1953) + * single state validator couldn't ever complete stateroot signature (#1953) + * SV vote resending was missing (#1953) + * SV vote messages used invalid (too big) ValidBlockEnd increment (#1953) + * memory leak in state validation service (#1953) + * NEP-6 wallets have `isDefault` field, not `isdefault` (#1961) + +## 0.94.1 "Channelization" (08 Apr 2021) + +This is the second and much improved N3 RC1-compatible release. We've mostly +focused on documentation and examples with this release, so there is a number +of updates in this area including oracle contract example and NEP-11 NFT +contract example. At the same time proper testnet network revealed some +implementation inconsistencies between NeoGo and C# especially in oracle and +state validation services, so there are fixes for them also. + +Protocol-wise this release is compatible with 0.94.0, but MPT structures have +changed and there are known state change differences for N3 RC1 testnet, so +you need to do full node resynchronization on update from 0.94.0. Some SDK +APIs have changed also improving developer experience, but they may affect +your code. We don't plan to make more of RC1-compatible releases (the protocol +has slightly changed already since the release). + +New features: + * RPC (*Client).GetDesignatedByRole method to easily get node lists from + RoleManagement contract (#1855) + * `calculatenetworkfee` RPC method support (#1858) + * RPC client now has additional methods for NEP-11 contracts and specifically + for NNS native contract (#1857) + * contract deployment from multisig addresses (#1886) + +Behavior changes: + * node roles for RoleManagement contract were moved into separate package + (#1855) + * NotaryVerificationPrice constant was moved into package of its own (#1855) + * testnet configuration now has proper N3 RC1 testnet magic (#1856) + * crypto.Verifiable interface was removed, now any hash.Hashable thing can be + verified (and signed, #1859) + * Network field was dropped from transaction.Transaction, block.Header, + payload.Extensible, state.MPTRoot, payload.P2PNotaryRequest and + network.Message, it was needed for proper hash calculations before recent + protocol changes (even though this field is not a part of serialized + representation of any of these elements), but now it's only used to + sign/verify data and doesn't affect hashing which allowed to simplify + interface here (#1859) + * RPC server now returns `StateRootInHeader` option in its `getversion` + request answers if it's used by the network (#1862) + * NNS record types were moved to separate package (#1857) + * MaxStorageKeyLen and MaxStorageValueLen constants were moved to storage + package from core (#1871) + * oracle service now accepts complete URL for other nodes RPC services (#1877) + +Improvements: + * RPC (*Client).AddNetworkFee method is now more informative in some error + case (#1852) + * proper NEP-17 support in unit test contract (#1852) + * documentation updates for examples and services (#1854, #1890) + * syscall number is now printed for failed syscalls (#1874) + * better logging (#1883, #1889) + * Oracle native contract interop documentation extended (#1884) + * Oracle native contract interop extended with return codes and constants (#1884) + * oracle smart contract example (#1884) + * NEP-11 NFT smart contract example (#1891) + +Bugs fixed: + * node could simultaneously try to connect to the same peer multiple times in + some cases (#1861) + * uniqueness is enforced now for other node addresses provided by peers (#1861) + * stateroot message hash wasn't calculated the same way as in C# (#1859) + * state validation service P2P messages were not signed properly (#1859) + * state.MPTRoot structure JSON representation was different from C# one (#1859) + * stateroot messages generated by state validation service were not setting + proper message category (#1866) + * block persistence caches were flushed too early by MPT-managing code (#1866) + * validated stateroot data overwrote local one which lead to node not + functioning after restart if states were different (#1866) + * peers delivering P2P message with validated stateroot differing from local + one were disconnected (#1866) + * state dump comparing script was using old Ledger native contract ID (#1869) + * candidate registration check was made in different way from C# in native + NEO contract leading to different execution results for erroneous voting + transactions (#1869) + * state change dumps were different from C# node for erroneous voting + transactions even though contract's state was the same (#1869) + * arguments were not completely removed from stack in erroneous + Runtime.Notify calls leading to different stack state with C# + implementation (#1874) + * in case contract failed execution with THROW the node didn't print the + message from the contract properly (#1874) + * `getproof` RPC call output was not strictly compliant with C# + implementation (#1871) + * MPT node serialization and some constants were out of date wrt C# + implementation (#1871) + * native NEP-17 contracts could use stale balance data in some cases (#1876) + * state validation service could lock the system during shutdown, preventing + proper node exit (#1877) + * state validation and oracle services were started before node reaching full + synchronization which could lead to excessive useless traffic (#1877) + * some native contract calls in function arguments could be miscompiled + (#1880) + * oracle service was accepting http URLs instead of https (#1883) + * neofs URIs were subject to host validation in oracle service even though + there is no host there (#1883) + * neofs URI scheme was differing from C# implementation (#1883) + * no default value was used for NeoFS request timeout if it's not specified + in the configuration (#1883) + * oracle response code was marshalled as integer into JSON (#1884) + * MPT could grow in memory unbounded (#1885) + * Storage.Find results could differ from C# in some cases (#1888) + +## 0.94.0 "Tsessebe" (19 Mar 2021) + +N3 RC1-compatible release is here. We've implemented all Neo protocol changes +(including state validation service and NeoFS support for oracles) and are +ready for testnet launch now, so that you could play with new native +contracts, VM instructions and other goodies RC1 brings with it. Some +usability improvements and documentation updates also went into this release +as well as a number of fixes stabilizing Notary subsystem (which is +NeoGo-specific protocol extension). + +Please note that this release is incompatible with 0.93.0. We do plan to make +an update soon (with more examples and documentation), but there won't be +long-term support provided, Neo N3 is still on its way to mainnet (although +RC1 and testnet are major milestones on this route). + +New features: + * Compiler: + - ellipsis support for append() to non-byte slices (#1750) + - NEP-11 standard conformance check (#1722) + - onNEP*Payable compliance checks (#1722) + * you can pass files as contract arguments now with `filebytes` CLI parameter + (#1762) + * CLI now supports specifying addresses everywhere script hashes are accepted + (#1758) + * System.Contract.CreateMultisigAccount interop function (#1763) + * SQRT and POW VM instructions (#1789, #1796) + * new NeoFSAlphabet node role added to RoleManagement contract (#1812) + * node can serve as state validator node (#1701) + * oracles now support NeoFS (#1484, #1830) + * CLI can be used to dump wallet's public keys (#1811) + * storage fee, candidate register price and oracle request price can now be + adjusted by committee (#1818, #1850) + * native contracts can now be versioned (#1827) + * RPC client was extended with price getters for native contracts (#1838) + +Behavior changes: + * NFTs no longer have "description" field (#1751) + * P2P Notary service configuration moved to ApplicationConfiguration section + (#1747) + * native contract methods requiring write permission in call flags now also + require read permission (#1777, #1837) + * System.Contract.Call interop function now requires state read permission + (#1777) + * NeoGo no longer supports Go 1.13 (#1791) + * native contract calls are now contract version aware (#1744) + * interop wrappers for smart contracts now use `int` type for all integers + (#1796) + * MaxTransactionsPerBlock, MaxBlockSize, MaxBlockSystemFee settings are now a + part of node configuration and no longer are available in Policy contract + (#1759, #1832) + * storage items can no longer be constant (#1819) + * state root handling is now conformant with C# implementation (with state + validators and vote/stateroot messages, #1701) + * blocks no longer have ConsensusData section, primary index is now a part of + the header (#1792, #1840, #1841) + * `wallet multisign sign` command was renamed to `wallet sign`, it now works + not just for multisignature contract signing, but also for multiple regular + signers as well as contract verification signing + * conversion interops were moved to StdLib native contract (#1824) + * crypto interops (except basic `CheckSig` and `CheckMultiSig`) were moved to + CryptoLib native contract (#1824) + * PACK, UNPACK and CONVERT instructions now cost more (#1826) + * some native contract types were adjusted (#1820) + * native Neo's `getCommittee` and `getNextBlockValidators` methods now cost + less (#1828) + * block/transaction/payload hashing and signing scheme has changed (#1829, + #1839) + * signing context is now serialized to JSON using base64 for data (#1829) + * System.Contract.IsStandard interop was removed (#1834) + * notifications are no longer allowed for safe contract methods (#1837) + +Improvements: + * verification script are now allowed to make contract calls (#1751) + * extensible payloads now have the same size limit as other P2P messages + (#1751) + * error logging is more helpful now in some cases (#1747, #1757) + * function inlining support in compiler for internal intrinsics, interop + refactoring (#1711, #1720, #1774, #1785, #1804, #1805, #1806, #1796, #1809) + * documentation updates (#1778, #1843) + * `sendrawtransaction` and `submitblock` RPC calls now return more detailed + information about errors for failed submissions (#1773) + * NeoGo now supports Go 1.16 (#1771, #1795) + * NEP-17 transfer tracking was optimized to avoid some DB accesses (#1782) + * interop wrappers added for SIGN/ABS/MAX/MIN/WITHIN instructions (#1796) + +Bugs fixed: + * `designateAsRole` method returned value while it shouldn't (#1746) + * deleting non-existent key could lead to node panic during MPT calculation + (#1754) + * some invalid IPv4 were accepted by name service contract (#1741) + * some legitimate IPv6 addresses were rejected by name service (#1741) + * compiler: append() to byte array produced wrong results for bytes with + >0x80 values (#1750) + * wrong notary-signed transaction size in notifications leading to client + disconnects (#1766) + * CALLT wasn't checking permission to read states and make calls in call + flags (#1777) + * proper escaping wasn't done in some cases on VM stack item conversion to + JSON (#1794) + * improper network fee calculation for complex multi-signed transactions in + some cases (#1797) + * importing package with the same name as compiled one could lead to + incorrect compiler behavior (#1808) + * boolean values were emitted as integers by compiler leading to some + comparison failures (#1822) + * `invokecontractverify` wasn't calculating proper GAS price of verification + using contract (#1825) + * ContractManagement contract wasn't returning NULL value from `getContract` + method if contract is missing (#1851) + +## 0.93.0 "Hyperproduction" (12 Feb 2021) + +This is a 3.0.0-preview5 compatible release with important protocol changes, +improved smart contract interop interface for native contracts (it's much +easier now to use them) and complete Notary subsystem which is a NeoGo +experimental protocol extension for P2P signature collection. + +Please note that this release is incompatible with 0.92.0 and there are no +plans for long-term support of it, Neo 3 is still changing and improving. + +The release is tested with preview5 testnet data (and one of testnet CNs is +already a neo-go node) up to 48K blocks and it has exactly the same storage +data except for the Ledger contract that technically can differ between nodes +(it's not a part of state proper), and in case of neo-go it's intentionally +different (but this doesn't affect contract's functionality and state roots +compatibility). + +New features: + * "ProtocolNotSupported" oracle response code (#1630) + * POPITEM VM opcode (#1670) + * CALLT VM opcode and contract tokens support (#1673) + * extensible P2P payloads (#1667, #1674, #1706, #1709) + * contract method overloading (#1689) + * oracle module (#1427, #1703) + * NNS native contract (#1678, #1712, #1721) + * complete P2P Notary subsytem (experimental protocol extension, use only on + private networks, #1658, #1726) + * Ledger native contract (#1696) + * `getblockheadercount` RPC call (#1718) + * native contract wrappers for Go smart contracts (#1702) + * `getnativecontracts` RPC call (#1724) + +Behavior changes: + * VM CLI now supports and requires manifests to properly work with NEF files + (#1642) + * NEP-17-related CLI commands now output GAS balance as floating point numbers + (#1654) + * `--from` parameter can be omitted now for NEP-17 CLI transfers, the default + wallet address will be used in this case (#1655) + * native contracts now use more specific types for methods arguments (#1657) + * some native contract names and IDs have changed (#1622, #1660) + * NEF file is stored now in contract's state instead of script only which + also affects RPC calls and ContractManagement's interface + * call flag definitions were moved to a package of their own (#1647) + * callback syscalls were removed (#1647) + * calling a contract now requires specifying allowed flags (and `CallEx` + syscall was removed, #1647) + * iterator/enumerator `Concat` calls were removed (#1656) + * `System.Enumerator.*` syscalls were removed (#1656) + * `System.Storage.Find` interface was reworked (#1656) + * NEF file format was changed merging compiler and version fields, adding + reserved fields and tokens (#1672) + * registering as committee/validator candidate now costs 1000 GAS (#1684) + * dbft now uses extensible P2P payloads (#1667) + * contract hashing scheme was changed now including names in the mix (#1686) + * GAS fees JSON marshalling was changed to plain integers (#1687, #1723) + * native methods requiring committee signature now fail the script instead of + returning `false` (#1695) + * native ContractManagement contract now has two `deploy` methods, with + additional data and without it (#1693) + * updated contract manifest now can't change contract's name (#1693) + * default values of native contracts were moved to storage from code (#1703) + * blocked accounts now store empty string instead of `01` byte (#1703) + * testnet magic number was changed for preview5 (#1709, #1715) + * `onPayment` method was renamed to `onNEP17Payment` (#1712) + * manifests are stored and accessed as stack items instead of JSON (#1704) + * zero-length "Headers" packet is a protocol error now (#1716) + * `getstorage` RPC call now uses base64 instead of hex for input/output + (#1717) + * state dumper and comparer now use base64 instead of hex (#1717) + * deployed contracts and manifests are now checked for correctness (#1729) + * transaction scripts are now checked for correctness before execution + (#1729) + +Improvements: + * default VM interop prices were adjusted (#1643) + * batched MPT updates (#1641) + * you can use `-m` now for manifest parameter of contract deploying command + (#1690) + * transaction cosigners can be specified with addresses now (#1690) + * compiler documentation was updated (#1690) + +Bugs fixed: + * oracle response transaction can't be correctly evicted from the mempool + (#1668) + * the node left with zero peers didn't initiate reconnections to seeds in rare + cases (#1671) + * native contracts supposed to check for committee witness in fact checked + for validators witness (#1679) + * it was allowed for contracts to have non-bool `verify` methods (#1689) + * previous proposal reuse could lead to empty blocks accepted even if there + are transactions in the mempool (#1707) + * NEO contract's `getCommittee` method name was misspelled (#1708) + * CLI wasn't correctly handling escape sequences (#1727) + * init and deploy methods could be misoptimized leading to execution failures + (#1730) + * NEP-17 contract example was missing `onNEP17Payment` invocation (#1732) + * missing state read privilege could lead to transaction failures for + transfers from CLI or when using native contract wrappers (#1734, #1735) + * some native contract methods had different parameter names and return + output types (#1736) + +## 0.92.0 "Fermentation" (28 Dec 2020) + +NeoGo project is closing year 2020 with 3.0.0-preview4 compatible release that +also has much improved performance, a lot of updates to compiler and SDK and +some experimental protocol extensions. This release also is the most tested +release of NeoGo ever, we've reached 83% code coverage (Neo 2.x only has +66%). + +Please note that this release is incompatible with 0.91.0 and there are no +plans for long-term support of it, Neo 3 is still changing and improving. + +Protocol-wise this release is tested with preview4 testnet (including working +in consensus with C# nodes) and it is compatible with it even though there are +some known storage change mismatches between NeoGo and C# (functionally the +contents is the same, this mismatch is caused by JSON handling differences and +needs to be addressed at the protocol level). + +New features: + * "high priority" transaction attribute (#1341) + * "oracle response" transaction attribute (#1407, #1520, #1573) + * new GAS distribution and committee update scheme (#1364, #1529, #1518, + #1542, #1549, #1636, #1639) + * oracle native contract (#1409, #1432, #1474, #1476, #1521, #1528, #1548, + #1556, #1625) + * designation native contract (#1451, #1504, #1519) + * support for `_deploy` method (#1452, #1466) + * `MaxTraceableBlocks` parameter can now be configured (#1520) + * `KeepOnlyLatestState` configuration option to drop old MPT data (#1553) + * `StateRootInHeader` configuration option to include state root data in the + header (it's a protocol extension, so use with care, #1500) + * NEP-5 was replaced by NEP-17 (#1558, #1574, #1617, #1636) + * `RemoveUntraceableBlocks` configuration option to perform old (unreachable) + block and transaction purging (#1561) + * stable deployed contract hashes (#1555, #1616, #1628) + * `Safe` method flag for contract manifests (#1597, #1607) + * native Management contract to deploy/update/destroy contracts (#1610, + #1624, #1636) + * Compiler/SDK: + - base58 encoding/decoding interops for smart contracts (#1350, #1375) + - you can use aliases now when importing interop packages (#397) + - `+=` operator is now supported for strings (#1351) + - `switch` without statement support (#1355) + - `make()` support for maps and slices using basic types (#1352) + - `copy()` support for byte slices (#1352, #1383) + - `iota` support (#1361) + - support for basic function literals (#1343) + - new variable initialization is now supported in `if` statements (#1343) + - `defer` is now supported (#1343) + - `recover()` support for exception handling (#1343, #1383) + - `delete()` support (#1391) + - `util.Remove()` function is added for smart contracts to remove elements + from slice (#1401) + - `interop` package now provides specific type aliases like Hash160/Hash256 + for smart contracts to use (it affects generated manifests, #1375, #1603) + - variables and function calls support in struct and slice literals (#1425) + - atoi/itoa encoding/decoding interops for smart contracts (#1530, #1533) + - contracts specifying NEP-17 as supported standard are now checked for + interface compliance (#1373) + - `contract.CallEx` function is now available to contracts that allows + specifying flags for contract called (#1598) + * CLI: + - support for array parameters in invocations (#1363, #1367) + - contract addresses can now be imported into wallet (#1362) + - deploying/invoking from multisignature accounts (#1461) + * RPC: + - `getcommittee` RPC call (#1416) + - limits and paging are now supported for `get*transfers` calls (#1419) + - `getunclaimedgas` RPC call now also supports passing address as a + parameter (#1418) + - `getcontractstate` now also accepts addresses, IDs and names (for native + contracts, #1426) + - batched JSON-RPC requests support (#1514) + - `invokecontractverify` RPC call to run verification scripts (#1524) + * P2P notaries subsystem (configurable `P2PSigExtensions` protocol extension, + use with care): + - optional `NotValidBefore` transaction attribute (#1490) + - optional `Conflicts` transaction attribute (#1507) + - native Notary contract (#1557, #1593) + - notary request P2P payload (#1582) + +Behavior changes: + * converting items to boolean now fail for strings of bytes longer than 32 + (#1346) + * consensus messages now use uint8 field for validator indexes (#1350) + * maximum possible try-catch nesting is now limited to 16 levels (#1347) + * maximum manifest size increased to 64K (#1365, #1555) + * required flags changed for many interop functions (#1362) + * compiler no longer generates code to log `panic()` message (#1343) + * `GetBlock`, `GetTransaction` and similar interop functions now return + pointers to structures (#1375) + * calling script hash now also is accounted for by CheckWitness interop + (#1435, #1439) + * CLI is using `--address` parameter everywhere it's needed (some commands + used `--addr` previously (#1434) + * VM now restricts comparable byte array size to 64K (#1433) + * `FeeOnly` witness scope was renamed to `None` (#1450) + * `getvalidators` RPC call was renamed to `getnextblockvalidators` (#1449) + * `emit.Opcode` is now `emit.Opcodes`, allowing for variadic specification of + opcodes (#1452) + * `CalculateNetworkFee` was moved to `fee.Calculate` (#1467) + * fault exception string is now returned for failed invocations (#1462) + * `runtime.GetInvocationCounter` no longer errors (#1455) + * `invoke*` RPC calls now also return `transaction` field (#1418) + * `getversion` RPC call now also returns network magic number (#1489) + * RPC calls now return data in base64 instead of hex (#1489, #1525, #1537) + * blocked accounts interface and storage was changed in Policy contract (#1504) + * voting fee is lower now (#1510) + * blocks are now processed with two execution triggers, `OnPersist` and + `PostPersist`, `getapplicationlog` RPC call was updated to support passing + trigger type (#1515, #1531, #1619) + * storage fee formula has changed (#1517) + * `MaxValidUntilBlockIncrement` is now 5760 instead of 2102400 (#1520) + * Policy contract no longer saves default values during initialization + (#1535) + * opcode pricing was changed and now it's adjustable (#1538, #1615) + * contracts no longer have `IsPayable` (see NEP-17) and `HasStorage` (they + all have it by default now) features (#1544) + * notification size is restricted now (#1551) + * unsolicited `addr` messages are now treated as errors (#1562) + * native contracts no longer have `name()` methods, it is now a part of + manifest (#1557) + * transaction fees, invocation GAS counters and unclaimed GAS values are now + returned as strings with floating point values from RPC calls (#1566, + #1572, #1604) + * NEF format was changed (#1555) + * `engine.AppCall()` interop was renamed to `contract.Call()` (#1598) + * call flags were renamed (#1608) + * deploying contract now costs at minimum 10 GAS and MaxGasInvoke + configuration was adjusted to account for that (the fee is configurable by + the committee, #1624, #1635) + +Improvements: + * a lot of new tests added (#1335, #1339, #1341, #1336, #1378, #1452, #1508, + #1564, #1567, #1583, #1584, #1587, #1590, #1591, #1595, #1602, #1633, + #1638) + * a number of optimizations across all node's components applied (#1342, + #1347, #1337, #1396, #1398, #1403, #1405, #1408, #1428, #1421, #1463, + #1471, #1492, #1493, #1526, #1561, #1618) + * smartcontract package now provides function to create simple majority + multisignature contract (in addition to BFT majority, #1341) + * `AddNetworkFee()` now supports contract addresses (#1362) + * error handling and help texts for CLI wallet commands (#1434) + * compiler emitting short jump instructions if possible (#805, #1488) + * compiler emitting jump instructions with embedded conditions where possible + (#1351) + * transaction retransmission mechanism added to mempool (#1536) + * parallel block fetching (#1568) + * Binary and Runtime interops refactored to separate packages (#1587) + * message broadcasts now finish successfully if the message is sent to 2/3 of + peers (#1637) + +Bugs fixed: + * TRY opcode implementation was not allowing for 0 offsets (#1347) + * compiler wasn't dropping unused elements returned from calls (#683) + * MEMCPY with non-zero destination index was not working correctly (#1352) + * asset transfer from CLI didn't work (#1354) + * specifying unknown DB type in configuration file induced node crash (#1356) + * address specifications in configuration file didn't work correctly (#1356) + * RPC server wasn't processing hashes with "0x" prefix in parameters (#1368) + * incorrect context unloding on exception handling (#1343) + * attempt to get committee only returned validators if there was no voting on + chain (#1370) + * block queue could be attacked with wrong blocks to cause OOM (#1374) + * token sale example wasn't checking witness correctly (#1379) + * structure methods were added to generated manifests (#1381) + * type conversions in `switch` and `range` statements were compiled as + function calls (#1383) + * RPC server result format wasn't conforming to C# implementation for + `getapplicationlog` and `invoke*` (#1371, #1387) + * subslicing non-byte slices was miscompiled (it's forbidden now, #1401) + * potential deadlock in consensus subsystem (#1410) + * race in peer connection closing method (#1378) + * race in MPT calculation functions (#1378) + * possible panic on shutdown (#1411) + * block-level `getapplicationlog` result had block hash in it (#1413) + * fail CN execution if wrong password is provided in the configuration + (#1419) + * tx witness check didn't account for GAS properly when several witnesses are + used (#1439) + * map keys longer than 64 bytes were allowed (#1433) + * unregistered candidate with zero votes wasn't removed (#1445) + * standard contract's network fee wasn't calculated correctly (#1446) + * witness checking wasn't taking into account transaction size fee (#1446) + * sending invalid transaction from the CLI wasn't prevented in some cases + (#1448, #1479, #1604) + * `System.Storage.Put` wasn't accounting for new key length in its GAS price + (#1458) + * blocks can't have more than 64K transactions (#1454) + * Policy native contract wasn't limiting some values (#1442) + * MerkleBlock payload wasn't serialized/deserialized properly (#1454, #1591) + * partial contract updates were not always possible (#1460) + * potential panic on verification with incorrect signature size (#1467) + * CLI attempted to save transaction when it wasn't requested (#1467) + * VM allowed to create bigger integers than it should support (#1464) + * some protocol limits were not enforced (#1469) + * iterating over storage items could produce incorrect KV pairs when using + LevelDB or BadgerDB (#1478) + * stale transaction were not deleted from the mempool (#1480) + * node panic during block processing with BoltDB (#1482) + * node that failed to connect to seeds on startup never attempted to + reconnect to them again (#1486, #1592) + * compiler produced incorrect code for if statements using function calls + (#1479) + * invocation stack size check wasn't performed in some cases (#1492) + * incorrect code produced by the compiler when assigning slices returned from + functions to new variables (#1495) + * websocket client closing connection on new events (#1494) + * minor `getrawtransaction`/`gettransactionheight` and NEP5-related RPC + implementation incompatibilities (#1543, #1550) + * VM CLI breakpoint wasn't stopping before the instruction (#1584) + * VM CLI was incorrectly processing missing parameter error (#1584) + * wallet conversion wasn't performed correctly (#1590) + * node didn't return all requested blocks in response to `getblocks` P2P + requests (#1595) + * CN didn't request transactions properly from its peers in some cases + (#1595) + * incorrect manifests generated for some parameter types (#1599) + * incorrect code generated when no global variables are present, but there + are some global constants (#1598) + * native contract invocations now set proper calling script hash (#1600) + * byte string and buffer VM stack items conversion to JSON differed from C# + (#1609) + * when mempool is full new transaction's hash could still be added into it + even if it is to be rejected afterwards (#1621) + * CN wasn't always performing timestamp validation correctly (#1620) + * incorrect stack contents after execution could stop block processing + (#1631) + * `getapplicationlog` RPC call handler wasn't validating its parameters + properly, potentially leading to node crash (#1636) + * a peer could be connected twice in rare circumstances (#1637) + * missing write timeout could lead to broadcasting stalls (#1637) + +## 0.91.0 "Ululation" (18 August 2020) + +We've updated NeoGo for 3.0.0-preview3 compatibility implementing all the +appropriate protocol changes as well as improving NeoGo-specific +components. This release brings with it significant changes for smart +contracts, both in terms of Neo protocol changes (no more there is a single +entry point! execution environment has also changed in lots of ways) and Go +smart contract compiler updates and fixes. + +Please note that this release is incompatible with 0.90.0 and there will be no +long-term support provided for it, Neo 3 is still changing and +improving. If you have any wallets used with 0.90.0 release you'll need to +regenerate them from private keys because of changes to verification scripts +that also changed hashes and addresses. + +But nonetheless it is tested to be compatible with preview3 testnet for up to +68K of blocks in terms of storage changes, with neo-debugger for debug data +produced by the compiler and with consensus process for heterogeneous setups +(like 2 neo-go CNs with 2 C# CNs). + +New features: + * secp256k1 signature checks added to interop functions + (Neo.Crypto.VerifyWithECDsaSecp256k1 and + Neo.Crypto.CheckMultisigWithECDsaSecp256k1 syscalls, + crypto.ECDsaSecp256k1Verify and crypto.ECDSASecp256k1CheckMultisig interop + functions, #918) + * RIPEMD160 hash added to interop functions (Neo.Crypto.RIPEMD160 syscall, + crypto.RIPEMD160 interop function, #918, #1193) + * "NotFound" P2P message (#1135, #1333) + * base64 encoding/decoding interop functions (binary.Base64Encode and + binary.Base64Decode, #1187) + * new contract.GetCallFlags interop (System.Contract.GetCallFlags syscall) + implemented (#1187) + * it is possible now to create iterators and enumerators over primitive VM + types in smart contracts (#1218) + * runtime.Platform interop (System.Runtime.Platform syscall) is now available + to smart contracts in Go (#1218) + * storage.PutEx interop (System.Storage.PutEx syscall) is now available to + smart contracts in Go (#1221) + * exceptions support in VM (#885) + * CLI conversion utility functions for addresses/hashes/etc (#1207, #1258) + * multitransfer transactions now can be generated with RPC client or CLI + (#940, #1260) + * System.Callback.* syscalls for callback creation and execution (callback.* + interop functions, #1197) + * MPT implementation was added (#1235) + * Policy native contract now also contains MaxBlockSystemFee setting (#1195) + * getting blocks by indexes via P2P is now supported (#1192) + * limited pointer support was added to the compiler (#1247) + * voting support in CLI (#1206, #1286) + +Behavior changes: + * crypto.ECDsaVerify interop function was renamed to + crypto.ECDsaSecp256r1Verify now that we have support for secp256k1 curve + (#918) + * many RPC requests/responses changed names used for data fields (#1169) + * runtime.Notify interop function now requires a mandatory UTF8 name + parameter (#1052) and this name can be used to filter notifications (#1266) + * sendrawtransaction and submitblock RPC calls now return a hash instead of + boolean value in case of success (#1216) + * System.Runtime.Log syscall now only accepts valid UTF8 strings no longer + than 1024 bytes (#1218) + * System.Storage.Put syscall now limits keys to 64 bytes and values to 1024 + bytes (#1221) + * PUSHA instruction now works with relative code offset (#1226) + * EQUAL instruction no longer does type conversions, so that values of + different types are always unequal (#1225) + * verification scripts now can't use more than 0.5 GAS (#1202) + * contracts no longer have single entry point, rather they export a set of + methods with specific offsets. Go smart contract compiler has been changed + accordingly to add all exported (as in Go) methods to the manifest + (but with the first letter being lowercased to match NEP-5 expectations, + #1228). Please also refer to examples changes to better see how it affects + contracts, manifests and configuration files (#1296) + * native contracts are now called via Neo.Native.Call syscall (#1191) + * compressed P2P payloads now also contain their uncompressed size (#1212, + #1255) + * NEF files now use double SHA256 for checksums (#1203) + * VM's map keys and contract methods now can only contain valid UTF-8 strings + (#1198) + * stack items now can be converted to/from JSON natively (without + smartcontract.ContractParameters intermediate) which is now used for + invoke* RPC calls and application execution logs (#1242, #1317) + * invoking Policy native contracts now requires AllowsStates (to get + settings) or AllowModifyStates (to change setting) flags (#1254) + * Transaction now has Signers field unifying Sender (the first Signer) and + Cosigners, a Signer can have FeeOnly or any other regular witness scope + (#1184) + * verification scripts no longer have access to blockchain's state (#1277) + * governance scheme was changed to delegated committee-based one. The number + of validators is now specified with ValidatorsCount configuration option, + standby validators are no longer being registered by default (#867, #1300) + * Go 1.13+ is now required to build neo-go (#1281) + * public contract methods now always return some value and this is being + checked by the VM (#1196, #1331, #1332) + * runtime interop package now exports triggers as proper constants rather + than functions (#1299) + * RPC client no longer has SetWIF/WIF methods that didn't do anything useful + anyway (#1328) + +Improvements: + * Neo.Crypto.CheckMultisigWithECDsaSecp256r1 syscall is now available via + crypto.ECDSASecp256r1CheckMultisig interop function (#1175) + * System.Contract.IsStandard syscall now also checks script's container (#1187) + * syscalls no longer have allowed triggers limitations (#1205) + * better testing coverage (#1232, #1318, #1328) + * getrawmempool RPC call now also supports verbose parameter (#1182) + * VMState is no longer being stored as a string for application execution + results (#1236) + * manifest now contains a list of supported standards (#1204) + * notifications can't be changed now by a contract after emitting them + (#1199) + * it is possible to call other contracts from native contracts now (#1271) + * getnep5transfers now supports timing parameters (#1289) + * smartcontract package now has CreateDefaultMultiSigRedeemScript that should + be used for BFT-compliant "m out of n" multisignature script generation + (#1300) + * validators are always sorted now (standby validators were not previously, + #1300) + * debug information now contains all file names (#1295) + * compiler now accepts directory to compile a package, only one file could be + passed previously (#1295) + * some old no longer used functions and structures were removed (#1303) + * contract inspection output was improved for new Neo 3 VM instructions (#1231) + * ping P2P message handling was changed to trigger block requests (#1326) + +Bugs fixed: + * inability to transfer NEO/GAS from deployed contract's address (#1180) + * System.Blockchain.GetTransactionFromBlock syscall didn't pick all of its + arguments from the stack in some error cases (#1187) + * System.Contract.CallEx syscall didn't properly check call flags (#1187) + * System.Blockchain.GetContract and System.Contract.Create syscalls returned + an interop interface instead of plain well-defined structure (#1187) + * System.Contract.Update syscall's manifest checks were improved, return + value was fixed (#1187) + * getnep5balances and getnep5transfers RPC calls now support addresses in + their parameters (#1188) + * rare panic during node's shutdown (#1188) + * System.Runtime.CheckWitness, System.Runtime.GetTime syscalls are only allowed to be called with + AllowStates flag (#1218) + * System.Runtime.GasLeft syscall result for test VM mode was wrong (#1218) + * getrawtransaction RPC call now also returns its VM state after execution + (#1183) + * getnep5balances and getnep5transfers RPC calls now correctly work for + migrated contracts (#1188, #1239) + * compiler now generates correct code for global variables from multiple + files (#1240) + * compiler now correctly supports exported contracts and variables in + packages (#1154) + * compiler no longer confuses functions with the same name from different + packages (#1150) + * MaxBlockSize policy setting was not enforced (#1254) + * missing scope check for signers (#1244) + * compiler now properly supports init() functions (#1253, #1295) + * getvalidators RPC call now returns zero-length array of validators when + there are no registered candidates instead of null value (#1300) + * events were not added to the debug data (#1311, #1315) + * RPC client's BalanceOf method was lacking account parameter (#1323) + * VM CLI debugging commands didn't really allow to step through the contract + (#1328) + * recovery message decoding created incorrect PrepareRequest payload that + lead to consensus failures (#1334) + +## 0.90.0 "Tantalization" (14 July 2020) + +The first Neo 3 compatible release of neo-go! We've targeted to make it +compatible with preview2 release of Neo 3, so it only contains features +available there, but at the same time this makes the node more useful until we +have some more up to date reference version. It's a completely different +network, so almost everything has changed and it's hard to describe it with +the same level of details we usually do (but we'll provide them for subsequent +releases where the changeset is going to be lower in size). Please note that +this is a preview-level release and there won't be long-term support provided +for it, Neo 3 is evolving and the next release won't be compatible with this +one. + +Main Neo 3 features in this release: + * no UTXO + * native contracts + * new VM + * scoped witnesses for transaction + * updated interop/syscalls set + * contract manifests + * more efficient P2P protocol + +Things that have also changed: + * transaction format + * block format + * address format + * wallets + * RPC protocol + * notification subsystem + * executable format output for compiler + +Compatibility level of this neo-go release: + * identical storage changes compared to C# node for 378K blocks of preview2 + testnet + * debugging info produced is compatible with preview2-compatible neo-debugger + * running consensus nodes in heterogeneous setup is possible (2 neo-go CNs + with 2 C# CNs, for example) + +Changes specific to neo-go: + * some CLI parameters like wallet path or RPC endpoint URL have been unified + across all commands and thus have changed in some of them (refer to CLI + help for details) + * as an extension we support post-preview2 cosigners parameter for + invokefunction RPC calls (see neo-project/neo-modules#260) + * Go compiler now supports comparisons with nil properly + * we no longer provide bootstrapping 6k block dump for private networks, you + have 30000000 GAS right in the genesis block and it's not hard to make use + of it (see + [neo-go-sc-wrkshp](https://github.com/nspcc-dev/neo-go-sc-wrkshp) for an + example of how to use it) + * we have a conversion tool for your old Neo 2 wallets (`wallet convert` + command), so you can reuse keys on Neo 3 networks + * util.Equals interop function may not function the way you expect it to due + to Neo VM changes, it still is an EQUAL opcode though. This interop may be + removed in the future. + +## Older versions + +Please refer to the [master-2.x branch +CHANGELOG](https://github.com/nspcc-dev/neo-go/tree/master-2.x/CHANGELOG.md) +for versions prior to 0.90.0 (that are Neo 2 compatible, unlike 0.90.0+ that +are Neo 3 compatible). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ec598ba --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +First, thank you for contributing! We love and encourage pull requests from everyone. Please +follow the guidelines: + +1. Check open [issues](https://github.com/nspcc-dev/neo-go/issues) and +[pull requests](https://github.com/nspcc-dev/neo-go/pulls) for existing discussions. +1. Open an issue first to discuss a new feature or enhancement. +1. Write tests and make sure the test suite passes locally and on CI. +1. When optimizing something, write benchmarks and attach the results: + ``` + go test -run - -bench BenchmarkYourFeature -count=10 ./... >old // on master + go test -run - -bench BenchmarkYourFeature -count=10 ./... >new // on your branch + benchstat old new + ``` + `benchstat` is described here https://godocs.io/golang.org/x/perf/cmd/benchstat. + +1. Open a pull request and reference the relevant issue(s). +1. Make sure your commits are logically separated and have good comments + explaining the details of your change. Add a package/file prefix to your + commit if that's applicable (like 'vm: fix ADD miscalculation on full + moon'). +1. After receiving a feedback, amend your commits or add new ones as + appropriate. +1. **Have fun!** diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1165fbe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Builder image +# Keep go version in sync with Build GA job. +FROM golang:1.25-alpine AS builder + +# Display go version for information purposes. +RUN go version + +RUN set -x \ + && apk add --no-cache git make \ + && mkdir -p /tmp + +COPY . /neo-go + +WORKDIR /neo-go + +ARG REPO=repository +ARG VERSION=dev + +RUN VERSION=$VERSION REPO=$REPO make build + +# Executable image +FROM alpine + +ARG VERSION=dev +LABEL version=$VERSION + +WORKDIR / + +COPY --from=builder /neo-go/config /config +COPY --from=builder /neo-go/.docker/privnet-entrypoint.sh /usr/bin/privnet-entrypoint.sh +COPY --from=builder /neo-go/bin/neo-go /usr/bin/neo-go +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +ENTRYPOINT ["/usr/bin/privnet-entrypoint.sh"] + +CMD ["node", "--config-path", "/config", "--privnet"] diff --git a/Dockerfile.wsc b/Dockerfile.wsc new file mode 100644 index 0000000..22e4e84 --- /dev/null +++ b/Dockerfile.wsc @@ -0,0 +1,37 @@ +# Builder image +# Keep go version in sync with Build GA job. +FROM golang:1.25.0-windowsservercore-ltsc2022 AS builder + +COPY . /neo-go + +WORKDIR /neo-go + +ARG REPO=repository +ARG VERSION=dev + +SHELL ["cmd", "/S", "/C"] +RUN go env -w CGO_ENABLED=0 +ENV GOGC=off + +RUN go build -trimpath -v -o ./bin/neo-go.exe -ldflags="-X %REPO%/pkg/config.Version=%VERSION%" ./cli/main.go + +# Executable image +FROM mcr.microsoft.com/windows/servercore:ltsc2022 + +ARG VERSION +LABEL version=%VERSION% + +WORKDIR / + +COPY --from=builder /neo-go/config /config +COPY --from=builder /neo-go/.docker/privnet-entrypoint.ps1 /usr/bin/privnet-entrypoint.ps1 +COPY --from=builder /neo-go/bin/neo-go.exe /usr/bin/neo-go.exe + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';", "$ProgressPreference = 'SilentlyContinue';"] + +# Check executable version. +RUN /usr/bin/neo-go.exe --version + +ENTRYPOINT ["powershell", "-File", "/usr/bin/privnet-entrypoint.ps1"] + +CMD ["node", "--config-path", "/config", "--privnet"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bcc0de6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2023 NeoSPCC (@nspcc-dev), Anthony De Meulemeester (@anthdm), City of Zion community (@CityOfZion) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9ac0ed --- /dev/null +++ b/Makefile @@ -0,0 +1,159 @@ +BRANCH = "master" +REPONAME = "tutus-chain" +NETMODE ?= "tutus-testnet" +BINARY=tutus +BINARY_PATH=./bin/$(BINARY)$(shell go env GOEXE) +GO_VERSION ?= 1.25 +DESTDIR = "" +SYSCONFIGDIR = "/etc" +BINDIR = "/usr/bin" +SYSTEMDUNIT_DIR = "/lib/systemd/system" +UNITWORKDIR = "/var/lib/tutus" + +IMAGE_SUFFIX="$(shell if [ "$(OS)" = Windows_NT ]; then echo "_WindowsServerCore"; fi)" +D_FILE ?= "$(shell if [ "$(OS)" = Windows_NT ]; then echo "Dockerfile.wsc"; else echo "Dockerfile"; fi)" +DC_FILE ?= ".docker/docker-compose.yml" # Single docker-compose for Ubuntu/WSC, should be kept in sync with ENV_IMAGE_TAG. +ENV_IMAGE_TAG="env_neo_go_image" + +REPO ?= "$(shell go list -m)" +VERSION ?= "$(shell git describe --tags --match "v*" --abbrev=8 2>/dev/null | sed -r 's,^v([0-9]+\.[0-9]+)\.([0-9]+)(-.*)?$$,\1 \2 \3,' | while read mm patch suffix; do if [ -z "$$suffix" ]; then echo $$mm.$$patch; else patch=`expr $$patch + 1`; echo $$mm.$${patch}-pre$$suffix; fi; done)" +MODVERSION ?= "$(shell cat go.mod | cat go.mod | sed -r -n -e 's|.*pkg/interop (.*)|\1|p')" +BUILD_FLAGS = "-X '$(REPO)/pkg/config.Version=$(VERSION)' -X '$(REPO)/cli/smartcontract.ModVersion=$(MODVERSION)'" + +IMAGE_REPO=tutus-one/tutus-chain + +DISABLE_NEOTEST_COVER=1 + +ROOT_DIR:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) +GOMODDIRS=$(dir $(shell find $(ROOT_DIR) -name go.mod)) + +# All of the targets are phony here because we don't really use make dependency +# tracking for files +.PHONY: build $(BINARY) deps image docker/$(BINARY) image-latest image-push image-push-latest clean-cluster \ + test vet lint fmt cover version gh-docker-vars + +build: deps + @echo "=> Building binary" + @set -x \ + && export GOGC=off \ + && export CGO_ENABLED=0 \ + && go build -trimpath -v -ldflags $(BUILD_FLAGS) -o ${BINARY_PATH} ./cli/main.go + +$(BINARY): build + +docker/$(BINARY): + @echo "=> Building binary using clean Docker environment" + @docker run --rm -t \ + -v `pwd`:/src \ + -w /src \ + -u "$$(id -u):$$(id -g)" \ + --env HOME=/src \ + golang:$(GO_VERSION) make $(BINARY) + +tutus.service: tutus.service.template + @sed -r -e 's_BINDIR_$(BINDIR)_' -e 's_UNITWORKDIR_$(UNITWORKDIR)_' -e 's_SYSCONFIGDIR_$(SYSCONFIGDIR)_' $< >$@ + +install: build tutus.service + @echo "=> Installing systemd service" + @mkdir -p $(DESTDIR)$(SYSCONFIGDIR)/tutus \ + && mkdir -p $(SYSTEMDUNIT_DIR) \ + && cp ./tutus.service $(SYSTEMDUNIT_DIR) \ + && cp ./config/protocol.tutus.yml $(DESTDIR)$(SYSCONFIGDIR)/tutus \ + && cp ./config/protocol.tutus.testnet.yml $(DESTDIR)$(SYSCONFIGDIR)/tutus \ + && install -m 0755 -t $(BINDIR) $(BINARY_PATH) \ + +postinst: install + @echo "=> Preparing directories and configs" + @id tutus || useradd -s /usr/sbin/nologin -d $(UNITWORKDIR) tutus \ + && mkdir -p $(UNITWORKDIR) \ + && chown -R tutus:tutus $(UNITWORKDIR) $(BINDIR)/tutus \ + && systemctl enable tutus.service + +image: deps + @echo "=> Building image" + @echo " Dockerfile: $(D_FILE)" + @echo " Tag: $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX)" + @docker build -f $(D_FILE) -t $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX) --build-arg REPO=$(REPO) --build-arg VERSION=$(VERSION) . + +image-latest: deps + @echo "=> Building image with 'latest' tag" + @echo " Dockerfile: Dockerfile" # Always use default Dockerfile for Ubuntu as `latest`. + @echo " Tag: $(IMAGE_REPO):latest" + @docker build -t $(IMAGE_REPO):latest --build-arg REPO=$(REPO) --build-arg VERSION=$(VERSION) . + +image-push: + @echo "=> Publish image" + @echo " Tag: $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX)" + @docker push $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX) + +image-push-latest: + @echo "=> Publish image for Ubuntu with 'latest' tag" + @docker push $(IMAGE_REPO):latest + +deps: + @CGO_ENABLED=0 \ + go mod download + @CGO_ENABLED=0 \ + go mod tidy -v + +version: + @echo $(VERSION) + +gh-docker-vars: + @echo "file=$(D_FILE)" + @echo "version=$(VERSION)" + @echo "repo=$(IMAGE_REPO)" + @echo "suffix=$(IMAGE_SUFFIX)" + +test: + @go test ./... -cover + +vet: + @go vet ./... + +.golangci.yml: + curl -L -o $@ https://github.com/nspcc-dev/.github/raw/master/.golangci.yml + +lint: .golangci.yml + @for dir in $(GOMODDIRS); do \ + (cd "$$dir" && golangci-lint run --config $(ROOT_DIR)/$< | sed -r "s,^,$$dir," | sed -r "s,^$(ROOT_DIR),,") \ + done + +fmt: + @gofmt -l -w -s $$(find . -type f -name '*.go'| grep -v "/vendor/") + +cover: + @go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./pkg/...,./cli/... + @go tool cover -html=coverage.txt -o coverage.html + +# --- Ubuntu/Windows environment --- +env_image: + @echo "=> Building env image" + @echo " Dockerfile: $(D_FILE)" + @echo " Tag: $(ENV_IMAGE_TAG)" + @docker build \ + -f $(D_FILE) \ + -t $(ENV_IMAGE_TAG) \ + --build-arg REPO=$(REPO) \ + --build-arg VERSION=$(VERSION) . + +env_up: + @echo "=> Bootup environment" + @echo " Docker-compose file: $(DC_FILE)" + @docker compose -f $(DC_FILE) up -d node_one node_two node_three node_four + +env_single: + @echo "=> Bootup environment" + @docker compose -f $(DC_FILE) up -d node_single + +env_down: + @echo "=> Stop environment" + @docker compose -f $(DC_FILE) down + +env_restart: + @echo "=> Stop and start environment" + @docker compose -f $(DC_FILE) restart + +env_clean: env_down + @echo "=> Cleanup environment" + @docker volume rm docker_volume_chain diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e390d8 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Tutus Chain + +Sovereign blockchain platform for government deployments. Forked from [NeoGo](https://github.com/nspcc-dev/neo-go). + +**Website:** tutus.one +**License:** Apache 2.0 + +## Overview + +Tutus is an independent blockchain designed for government-grade deployments with: + +- **Sovereign instances** - Each government runs their own chain +- **1-second blocks** - Fast finality with dBFT consensus +- **7 validators** - Government-controlled consensus nodes +- **Zero gas for citizens** - Native contracts subsidize operations +- **Inter-government bridge** - Optional cross-border interoperability + +## Building + +Requires Go 1.24+ and `make`: + +```bash +make build +``` + +The resulting binary is `bin/tutus`. + +## Running a Node + +Start a Tutus node: + +```bash +./bin/tutus node --config-file ./config/protocol.tutus.yml +``` + +For testnet: + +```bash +./bin/tutus node --config-file ./config/protocol.tutus.testnet.yml +``` + +## Network Flags + +- `--tutus` - Tutus mainnet (government deployment) +- `--tutus-testnet` - Tutus testnet + +## Configuration + +Network configurations are in `./config/`: + +| File | Purpose | +|------|---------| +| `protocol.tutus.yml` | Production government deployment | +| `protocol.tutus.testnet.yml` | Development testnet | + +## Docker + +```bash +docker build -t tutus-chain . +docker run -d --name tutus -p 10333:10333 -p 10332:10332 tutus-chain +``` + +## Native Contracts (Planned) + +The following will be built into the Tutus protocol: + +| Contract | Purpose | +|----------|---------| +| PersonToken | Soul-bound identity | +| Scire | Universal education | +| Salus | Universal healthcare | +| Sese | Life planning | +| Tribute | Anti-hoarding economics | +| VTS | Government stablecoin | +| Eligere | Democratic voting | + +## Validator Setup + +See [docs/validator.md](docs/validator.md) for government validator deployment. + +## Origin + +Tutus is forked from NeoGo, the Go implementation of the Neo N3 blockchain. +We maintain compatibility with the Neo VM while extending the protocol for government use cases. + +## License + +Apache 2.0 - See [LICENSE.md](LICENSE.md) diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..39e36a7 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,36 @@ +# Roadmap for neo-go + +This defines approximate plan of neo-go releases and key features planned for +them. Things can change if there is a need to push a bugfix or some critical +functionality. + +## Versions 0.7X.Y (as needed) +* Neo 2.0 support (bug fixes, minor functionality additions) + +## Version 0.115.0 (~Dec 2025) + * protocol updates + * bug fixes + +## Version 1.0 (2026, TBD) + * stable version + +# Deprecated functionality + +As the node and the protocol evolve some external APIs can change. Usually we +try keeping backwards compatibility for some time (like half a year) unless +it's impossible to do for some reason. But eventually old +APIs/commands/configurations will be removed and here is a list of scheduled +breaking changes. Consider changing your code/scripts/configurations if you're +using anything mentioned here. + +## Candidate registration via `registerCandidate` method of native NeoToken contract + +The original way of Neo candidate registration via `wallet candidate register` CLI +command using `registerCandidate` method of native NeoToken contract is deprecated +and has been superseded by the GAS transfer approach. Deprecated candidate +registration way via `registerCandidate` method call is supported via +`--useRegisterCall` flag. + +Removal of `registerCandidate`–based support of candidate registration will be +done once `registerCandidate` method is officially deprecated and removed from +the NeoToken manifest with the subsequent hardfork. diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..573c0c4 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1 @@ +cli diff --git a/cli/app/app.go b/cli/app/app.go new file mode 100644 index 0000000..aaee0d0 --- /dev/null +++ b/cli/app/app.go @@ -0,0 +1,41 @@ +package app + +import ( + "fmt" + "os" + "runtime" + + "github.com/nspcc-dev/neo-go/cli/query" + "github.com/nspcc-dev/neo-go/cli/server" + "github.com/nspcc-dev/neo-go/cli/smartcontract" + "github.com/nspcc-dev/neo-go/cli/util" + "github.com/nspcc-dev/neo-go/cli/vm" + "github.com/nspcc-dev/neo-go/cli/wallet" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/urfave/cli/v2" +) + +func versionPrinter(c *cli.Context) { + _, _ = fmt.Fprintf(c.App.Writer, "NeoGo\nVersion: %s\nGoVersion: %s\n", + config.Version, + runtime.Version(), + ) +} + +// New creates a NeoGo instance of [cli.App] with all commands included. +func New() *cli.App { + cli.VersionPrinter = versionPrinter + ctl := cli.NewApp() + ctl.Name = "neo-go" + ctl.Version = config.Version + ctl.Usage = "Official Go client for Neo" + ctl.ErrWriter = os.Stdout + + ctl.Commands = append(ctl.Commands, server.NewCommands()...) + ctl.Commands = append(ctl.Commands, smartcontract.NewCommands()...) + ctl.Commands = append(ctl.Commands, wallet.NewCommands()...) + ctl.Commands = append(ctl.Commands, vm.NewCommands()...) + ctl.Commands = append(ctl.Commands, util.NewCommands()...) + ctl.Commands = append(ctl.Commands, query.NewCommands()...) + return ctl +} diff --git a/cli/app/main_test.go b/cli/app/main_test.go new file mode 100644 index 0000000..a8521f3 --- /dev/null +++ b/cli/app/main_test.go @@ -0,0 +1,19 @@ +package app_test + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/internal/versionutil" + "github.com/nspcc-dev/neo-go/pkg/config" +) + +func TestCLIVersion(t *testing.T) { + config.Version = versionutil.TestVersion // Zero-length version string disables '--version' completely. + e := testcli.NewExecutor(t, false) + e.Run(t, "neo-go", "--version") + e.CheckNextLine(t, "^NeoGo") + e.CheckNextLine(t, "^Version:") + e.CheckNextLine(t, "^GoVersion:") + e.CheckEOF(t) +} diff --git a/cli/cmdargs/parser.go b/cli/cmdargs/parser.go new file mode 100644 index 0000000..713cc5d --- /dev/null +++ b/cli/cmdargs/parser.go @@ -0,0 +1,360 @@ +package cmdargs + +import ( + "errors" + "fmt" + "strings" + + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/urfave/cli/v2" +) + +const ( + // CosignersSeparator marks the start of cosigners cli args. + CosignersSeparator = "--" + // ArrayStartSeparator marks the start of array cli arg. + ArrayStartSeparator = "[" + // ArrayEndSeparator marks the end of array cli arg. + ArrayEndSeparator = "]" +) + +const ( + // ParamsParsingDoc is a documentation for parameters parsing. + ParamsParsingDoc = ` Arguments always do have regular Neo smart contract parameter types, either + specified explicitly or being inferred from the value. To specify the type + manually use "type:value" syntax where the type is one of the following: + 'signature', 'bool', 'int', 'hash160', 'hash256', 'bytes', 'key' or 'string'. + Array types are also supported: use special space-separated '[' and ']' + symbols around array values to denote array bounds. Nested arrays are also + supported. Null parameter is supported via 'nil' keyword without additional + type specification. + + There is ability to provide an argument of 'bytearray' type via file. Use a + special 'filebytes' argument type for this with a filepath specified after + the colon, e.g. 'filebytes:my_file.txt'. + + Given values are type-checked against given types with the following + restrictions applied: + * 'signature' type values should be hex-encoded and have a (decoded) + length of 64 bytes. + * 'bool' type values are 'true' and 'false'. + * 'int' values are decimal integers that can be successfully converted + from the string. + * 'hash160' values are Neo addresses and hex-encoded 20-bytes long (after + decoding) strings. + * 'hash256' type values should be hex-encoded and have a (decoded) + length of 32 bytes. + * 'bytes' type values are any hex-encoded things. + * 'filebytes' type values are filenames with the argument value inside. + * 'key' type values are hex-encoded marshalled public keys. + * 'string' type values are any valid UTF-8 strings. In the value's part of + the string the colon looses it's special meaning as a separator between + type and value and is taken literally. + + If no type is explicitly specified, it is inferred from the value using the + following logic: + - anything that can be interpreted as a decimal integer gets + an 'int' type + - 'nil' string gets 'Any' NEP-14 parameter type and nil value which corresponds + to Null stackitem + - 'true' and 'false' strings get 'bool' type + - valid Neo addresses and 20 bytes long hex-encoded strings get 'hash160' + type + - valid hex-encoded public keys get 'key' type + - 32 bytes long hex-encoded values get 'hash256' type + - 64 bytes long hex-encoded values get 'signature' type + - any other valid hex-encoded values get 'bytes' type + - anything else is a 'string' + + Backslash character is used as an escape character and allows to use colon in + an implicitly typed string. For any other characters it has no special + meaning, to get a literal backslash in the string use the '\\' sequence. + + Examples: + * 'int:42' is an integer with a value of 42 + * '42' is an integer with a value of 42 + * 'nil' is a parameter with Any NEP-14 type and nil value (corresponds to Null stackitem) + * 'bad' is a string with a value of 'bad' + * 'dead' is a byte array with a value of 'dead' + * 'string:dead' is a string with a value of 'dead' + * 'filebytes:my_data.txt' is bytes decoded from a content of my_data.txt + * 'NSiVJYZej4XsxG5CUpdwn7VRQk8iiiDMPM' is a hash160 with a value + of '682cca3ebdc66210e5847d7f8115846586079d4a' + * '\4\2' is an integer with a value of 42 + * '\\4\2' is a string with a value of '\42' + * 'string:string' is a string with a value of 'string' + * 'string\:string' is a string with a value of 'string:string' + * '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a + key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' + * '[ a b c ]' is an array with strings values 'a', 'b' and 'c' + * '[ a b [ c d ] e ]' is an array with 4 values: string 'a', string 'b', + array of two strings 'c' and 'd', string 'e' + * '[ ]' is an empty array` + + // SignersParsingDoc is a documentation for signers parsing. + SignersParsingDoc = ` Signers represent a set of Uint160 hashes with witness scopes and are used + to verify hashes in System.Runtime.CheckWitness syscall. First signer is treated + as a sender. To specify signers use signer[:scope] syntax where + * 'signer' is a signer's address (as Neo address or hex-encoded 160 bit (20 byte) + LE value with or without '0x' prefix). + * 'scope' is a comma-separated set of cosigner's scopes, which could be: + - 'None' - default witness scope which may be used for the sender + to only pay fee for the transaction. + - 'Global' - allows this witness in all contexts. This cannot be combined + with other flags. + - 'CalledByEntry' - means that this condition must hold: EntryScriptHash + == CallingScriptHash. The witness/permission/signature + given on first invocation will automatically expire if + entering deeper internal invokes. This can be default + safe choice for native NEO/GAS. + - 'CustomContracts' - define valid custom contract hashes for witness check. + Hashes are be provided as hex-encoded LE value string. + At lest one hash must be provided. Multiple hashes + are separated by ':'. + - 'CustomGroups' - define custom public keys for group members. Public keys are + provided as short-form (1-byte prefix + 32 bytes) hex-encoded + values. At least one key must be provided. Multiple keys + are separated by ':'. + + If no scopes were specified, 'CalledByEntry' used as default. If no signers were + specified, no array is passed. Note that scopes are properly handled by + neo-go RPC server only. C# implementation does not support scopes capability. + + Examples: + * 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5' + * 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global' + * '0x0000000009070e030d0f0e020d0c06050e030c02' + * '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` + + `CustomGroups:0206d7495ceb34c197093b5fc1cccf1996ada05e69ef67e765462a7f5d88ee14d0' + * '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` + + `CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02'` +) + +// GetSignersFromContext returns signers parsed from context args starting +// from the specified offset. +func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, cli.ExitCoder) { + args := ctx.Args() + var ( + signers []transaction.Signer + err error + ) + if args.Present() && args.Len() > offset { + signers, err = ParseSigners(args.Slice()[offset:]) + if err != nil { + return nil, cli.Exit(err, 1) + } + } + return signers, nil +} + +// ParseSigners returns array of signers parsed from their string representation. +func ParseSigners(args []string) ([]transaction.Signer, error) { + var signers []transaction.Signer + for i, c := range args { + cosigner, err := parseCosigner(c) + if err != nil { + return nil, fmt.Errorf("failed to parse signer #%d: %w", i, err) + } + signers = append(signers, cosigner) + } + return signers, nil +} + +func parseCosigner(c string) (transaction.Signer, error) { + var ( + err error + res = transaction.Signer{ + Scopes: transaction.CalledByEntry, + } + ) + address, scopes, _ := strings.Cut(c, ":") + res.Account, err = flags.ParseAddress(address) + if err != nil { + return res, err + } + + if scopes == "" { + return res, nil + } + + res.Scopes = 0 + for s := range strings.SplitSeq(scopes, ",") { + sub := strings.Split(s, ":") + scope, err := transaction.ScopesFromString(sub[0]) + if err != nil { + return transaction.Signer{}, err + } + if scope == transaction.Global && res.Scopes&^transaction.Global != 0 || + scope != transaction.Global && res.Scopes&transaction.Global != 0 { + return transaction.Signer{}, errors.New("'Global' scope can not be combined with other scopes") + } + + res.Scopes |= scope + + switch scope { + case transaction.CustomContracts: + if len(sub) == 1 { + return transaction.Signer{}, errors.New("CustomContracts scope must refer to at least one contract") + } + for _, s := range sub[1:] { + addr, err := flags.ParseAddress(s) + if err != nil { + return transaction.Signer{}, err + } + + res.AllowedContracts = append(res.AllowedContracts, addr) + } + case transaction.CustomGroups: + if len(sub) == 1 { + return transaction.Signer{}, errors.New("CustomGroups scope must refer to at least one group") + } + for _, s := range sub[1:] { + pub, err := keys.NewPublicKeyFromString(s) + if err != nil { + return transaction.Signer{}, err + } + + res.AllowedGroups = append(res.AllowedGroups, pub) + } + default: + } + } + return res, nil +} + +// GetDataFromContext returns data parameter from context args. +func GetDataFromContext(ctx *cli.Context) (int, any, cli.ExitCoder) { + var ( + data any + offset int + params []smartcontract.Parameter + err error + ) + args := ctx.Args() + if args.Present() { + offset, params, err = ParseParams(args.Slice(), true) + if err != nil { + return offset, nil, cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) + } + if len(params) > 1 { + return offset, nil, cli.Exit("'data' should be represented as a single parameter", 1) + } + if len(params) != 0 { + data, err = smartcontract.ExpandParameterToEmitable(params[0]) + if err != nil { + return offset, nil, cli.Exit(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1) + } + } + } + return offset, data, nil +} + +// EnsureNone returns an error if there are any positional arguments present. +// It can be used to check for them in commands that don't accept arguments. +func EnsureNone(ctx *cli.Context) cli.ExitCoder { + if ctx.Args().Present() { + return cli.Exit(fmt.Errorf("additional arguments given while this command expects none"), 1) + } + return nil +} + +// ParseParams extracts array of smartcontract.Parameter from the given args and +// returns the number of handled words, the array itself and an error. +// `calledFromMain` denotes whether the method was called from the outside or +// recursively and used to check if CosignersSeparator and ArrayEndSeparator are +// allowed to be in `args` sequence. +func ParseParams(args []string, calledFromMain bool) (int, []smartcontract.Parameter, error) { + res := []smartcontract.Parameter{} + for k := 0; k < len(args); { + s := args[k] + switch s { + case CosignersSeparator: + if calledFromMain { + return k + 1, res, nil // `1` to convert index to numWordsRead + } + return 0, []smartcontract.Parameter{}, errors.New("invalid array syntax: missing closing bracket") + case ArrayStartSeparator: + numWordsRead, array, err := ParseParams(args[k+1:], false) + if err != nil { + return 0, nil, fmt.Errorf("failed to parse array: %w", err) + } + res = append(res, smartcontract.Parameter{ + Type: smartcontract.ArrayType, + Value: array, + }) + k += 1 + numWordsRead // `1` for opening bracket + case ArrayEndSeparator: + if calledFromMain { + return 0, nil, errors.New("invalid array syntax: missing opening bracket") + } + return k + 1, res, nil // `1`to convert index to numWordsRead + default: + param, err := smartcontract.NewParameterFromString(s) + if err != nil { + // '--' argument is skipped by urfave/cli library, which leads + // to [--, addr:scope] being transformed to [addr:scope] and + // interpreted as a parameter if other positional arguments are not present. + // Here we fallback to parsing cosigners in this specific case to + // create a better user experience ('-- addr:scope' vs '-- -- addr:scope'). + if k == 0 { + if _, err := parseCosigner(s); err == nil { + return 0, nil, nil + } + } + return 0, nil, fmt.Errorf("failed to parse argument #%d: %w", k+1, err) + } + res = append(res, *param) + k++ + } + } + if calledFromMain { + return len(args), res, nil + } + return 0, []smartcontract.Parameter{}, errors.New("invalid array syntax: missing closing bracket") +} + +// GetSignersAccounts returns the list of signers combined with the corresponding +// accounts from the provided wallet. +func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers []transaction.Signer, accScope transaction.WitnessScope) ([]actor.SignerAccount, error) { + signersAccounts := make([]actor.SignerAccount, 0, len(signers)+1) + sender := senderAcc.ScriptHash() + signersAccounts = append(signersAccounts, actor.SignerAccount{ + Signer: transaction.Signer{ + Account: sender, + Scopes: accScope, + }, + Account: senderAcc, + }) + for i, s := range signers { + if s.Account == sender { + signersAccounts[0].Signer = s + continue + } + signerAcc := wall.GetAccount(s.Account) + if signerAcc == nil { + return nil, fmt.Errorf("no account was found in the wallet for signer #%d (%s)", i, address.Uint160ToString(s.Account)) + } + signersAccounts = append(signersAccounts, actor.SignerAccount{ + Signer: s, + Account: signerAcc, + }) + } + return signersAccounts, nil +} + +// EnsureNotEmpty returns a function that checks if the flag with the given name +// is not empty. +func EnsureNotEmpty(flagName string) func(*cli.Context, string) error { + return func(ctx *cli.Context, name string) error { + if ctx.String(flagName) == "" { + return cli.Exit(fmt.Errorf("required flag --%s is empty", flagName), 1) + } + return nil + } +} diff --git a/cli/cmdargs/parser_test.go b/cli/cmdargs/parser_test.go new file mode 100644 index 0000000..f3d8521 --- /dev/null +++ b/cli/cmdargs/parser_test.go @@ -0,0 +1,379 @@ +package cmdargs + +import ( + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestParseCosigner(t *testing.T) { + acc := util.Uint160{1, 3, 5, 7} + c1, c2 := random.Uint160(), random.Uint160() + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + + testCases := map[string]transaction.Signer{ + acc.StringLE(): { + Account: acc, + Scopes: transaction.CalledByEntry, + }, + "0x" + acc.StringLE(): { + Account: acc, + Scopes: transaction.CalledByEntry, + }, + acc.StringLE() + ":Global": { + Account: acc, + Scopes: transaction.Global, + }, + acc.StringLE() + ":CalledByEntry": { + Account: acc, + Scopes: transaction.CalledByEntry, + }, + acc.StringLE() + ":None": { + Account: acc, + Scopes: transaction.None, + }, + acc.StringLE() + ":CalledByEntry,CustomContracts:" + c1.StringLE() + ":0x" + c2.StringLE(): { + Account: acc, + Scopes: transaction.CalledByEntry | transaction.CustomContracts, + AllowedContracts: []util.Uint160{c1, c2}, + }, + acc.StringLE() + ":CustomGroups:" + priv.PublicKey().StringCompressed(): { + Account: acc, + Scopes: transaction.CustomGroups, + AllowedGroups: keys.PublicKeys{priv.PublicKey()}, + }, + } + for s, expected := range testCases { + actual, err := parseCosigner(s) + require.NoError(t, err) + require.Equal(t, expected, actual, s) + } + errorCases := []string{ + acc.StringLE() + "0", + acc.StringLE() + ":Unknown", + acc.StringLE() + ":Global,CustomContracts", + acc.StringLE() + ":Global,None", + acc.StringLE() + ":CustomContracts:" + acc.StringLE() + ",Global", + acc.StringLE() + ":CustomContracts", + acc.StringLE() + ":CustomContracts:xxx", + acc.StringLE() + ":CustomGroups", + acc.StringLE() + ":CustomGroups:xxx", + } + for _, s := range errorCases { + _, err := parseCosigner(s) + require.Error(t, err, s) + } +} + +func TestParseParams_CalledFromItself(t *testing.T) { + testCases := map[string]struct { + WordsRead int + Value []smartcontract.Parameter + }{ + "]": { + WordsRead: 1, + Value: []smartcontract.Parameter{}, + }, + "[ [ ] ] ]": { + WordsRead: 5, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{}, + }, + }, + }, + }, + }, + "a b c ]": { + WordsRead: 4, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.StringType, + Value: "b", + }, + { + Type: smartcontract.StringType, + Value: "c", + }, + }, + }, + "a [ b [ [ c d ] e ] ] f ] extra items": { + WordsRead: 13, // the method should return right after the last bracket, as calledFromMain == false + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "b", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "c", + }, + { + Type: smartcontract.StringType, + Value: "d", + }, + }, + }, + { + Type: smartcontract.StringType, + Value: "e", + }, + }, + }, + }, + }, + { + Type: smartcontract.StringType, + Value: "f", + }, + }, + }, + } + + for str, expected := range testCases { + input := strings.Split(str, " ") + offset, actual, err := ParseParams(input, false) + require.NoError(t, err) + require.Equal(t, expected.WordsRead, offset) + require.Equal(t, expected.Value, actual) + } + + errorCases := []string{ + "[ ]", + "[ a b [ c ] d ]", + "[ ] --", + "--", + "not-int:integer ]", + } + + for _, str := range errorCases { + input := strings.Split(str, " ") + _, _, err := ParseParams(input, false) + require.Error(t, err) + } +} + +func TestParseParams_CalledFromOutside(t *testing.T) { + testCases := map[string]struct { + WordsRead int + Parameters []smartcontract.Parameter + }{ + "-- cosigner1": { + WordsRead: 1, // the `--` only + Parameters: []smartcontract.Parameter{}, + }, + "a b c": { + WordsRead: 3, + Parameters: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.StringType, + Value: "b", + }, + { + Type: smartcontract.StringType, + Value: "c", + }, + }, + }, + "a b c -- cosigner1": { + WordsRead: 4, + Parameters: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.StringType, + Value: "b", + }, + { + Type: smartcontract.StringType, + Value: "c", + }, + }, + }, + "a [ b [ [ c d ] e ] ] f": { + WordsRead: 12, + Parameters: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "b", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "c", + }, + { + Type: smartcontract.StringType, + Value: "d", + }, + }, + }, + { + Type: smartcontract.StringType, + Value: "e", + }, + }, + }, + }, + }, + { + Type: smartcontract.StringType, + Value: "f", + }, + }, + }, + "a [ b ] -- cosigner1 cosigner2": { + WordsRead: 5, + Parameters: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "b", + }, + }, + }, + }, + }, + "a [ b ]": { + WordsRead: 4, + Parameters: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "b", + }, + }, + }, + }, + }, + "a [ b ] [ [ c ] ] [ [ [ d ] ] ]": { + WordsRead: 16, + Parameters: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "a", + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "b", + }, + }, + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "c", + }, + }, + }, + }, + }, + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + Value: []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: "d", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + for str, expected := range testCases { + input := strings.Split(str, " ") + offset, arr, err := ParseParams(input, true) + require.NoError(t, err) + require.Equal(t, expected.WordsRead, offset) + require.Equal(t, expected.Parameters, arr) + } + + errorCases := []string{ + "[", + "]", + "[ [ ]", + "[ [ ] --", + "[ -- ]", + } + for _, str := range errorCases { + input := strings.Split(str, " ") + _, _, err := ParseParams(input, true) + require.Error(t, err) + } +} diff --git a/cli/flags/address.go b/cli/flags/address.go new file mode 100644 index 0000000..dc62cf0 --- /dev/null +++ b/cli/flags/address.go @@ -0,0 +1,146 @@ +package flags + +import ( + "flag" + "fmt" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/urfave/cli/v2" +) + +// Address is a wrapper for a Uint160 with flag.Value methods. +type Address struct { + IsSet bool + Value util.Uint160 +} + +// AddressFlag is a flag with type Uint160. +type AddressFlag struct { + Name string + Usage string + Value Address + Aliases []string + Required bool + Hidden bool + Action func(*cli.Context, string) error +} + +var ( + _ flag.Value = (*Address)(nil) + _ cli.Flag = AddressFlag{} +) + +// String implements the fmt.Stringer interface. +func (a Address) String() string { + return address.Uint160ToString(a.Value) +} + +// Set implements the flag.Value interface. +func (a *Address) Set(s string) error { + addr, err := ParseAddress(s) + if err != nil { + return cli.Exit(err, 1) + } + a.IsSet = true + a.Value = addr + return nil +} + +// Uint160 casts an address to Uint160. +func (a *Address) Uint160() (u util.Uint160) { + if !a.IsSet { + // It is a programmer error to call this method without + // checking if the value was provided. + panic("address was not set") + } + return a.Value +} + +// IsSet checks if flag was set to a non-default value. +func (f AddressFlag) IsSet() bool { + return f.Value.IsSet +} + +// String returns a readable representation of this value +// (for usage defaults). +func (f AddressFlag) String() string { + var names []string + for _, name := range f.Names() { + names = append(names, getNameHelp(name)) + } + + return strings.Join(names, ", ") + "\t" + f.Usage +} + +func getNameHelp(name string) string { + if len(name) == 1 { + return fmt.Sprintf("-%s value", name) + } + return fmt.Sprintf("--%s value", name) +} + +// Names returns the names of the flag. +func (f AddressFlag) Names() []string { + return cli.FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether the flag is required. +func (f AddressFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false. +func (f AddressFlag) IsVisible() bool { + return !f.Hidden +} + +// TakesValue returns true of the flag takes a value, otherwise false. +func (f AddressFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag. +func (f AddressFlag) GetUsage() string { + return f.Usage +} + +// Apply populates the flag given the flag set and environment. +// Ignores errors. +func (f AddressFlag) Apply(set *flag.FlagSet) error { + for _, name := range f.Names() { + set.Var(&f.Value, name, f.Usage) + } + return nil +} + +// RunAction executes flag action if set. +func (f AddressFlag) RunAction(c *cli.Context) error { + if f.Action != nil { + return f.Action(c, address.Uint160ToString(f.Value.Value)) + } + return nil +} + +// GetValue returns the flags value as string representation. +func (f AddressFlag) GetValue() string { + return address.Uint160ToString(f.Value.Value) +} + +// Get returns the flag’s value in the given Context. +func (f AddressFlag) Get(ctx *cli.Context) Address { + adr := ctx.Generic(f.Name).(*Address) + return *adr +} + +// ParseAddress parses a Uint160 from either an LE string or an address. +func ParseAddress(s string) (util.Uint160, error) { + const uint160size = 2 * util.Uint160Size + switch len(s) { + case uint160size, uint160size + 2: + return util.Uint160DecodeStringLE(strings.TrimPrefix(s, "0x")) + default: + return address.StringToUint160(s) + } +} diff --git a/cli/flags/address_test.go b/cli/flags/address_test.go new file mode 100644 index 0000000..349c74d --- /dev/null +++ b/cli/flags/address_test.go @@ -0,0 +1,211 @@ +package flags + +import ( + "flag" + "io" + "testing" + + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func TestParseAddress(t *testing.T) { + expected := random.Uint160() + t.Run("simple LE", func(t *testing.T) { + u, err := ParseAddress(expected.StringLE()) + require.NoError(t, err) + require.Equal(t, expected, u) + }) + t.Run("with prefix", func(t *testing.T) { + u, err := ParseAddress("0x" + expected.StringLE()) + require.NoError(t, err) + require.Equal(t, expected, u) + + t.Run("bad", func(t *testing.T) { + _, err := ParseAddress("0s" + expected.StringLE()) + require.Error(t, err) + }) + }) + t.Run("address", func(t *testing.T) { + addr := address.Uint160ToString(expected) + u, err := ParseAddress(addr) + require.NoError(t, err) + require.Equal(t, expected, u) + + t.Run("bad", func(t *testing.T) { + _, err := ParseAddress(addr[1:]) + require.Error(t, err) + }) + }) +} + +func TestAddress_String(t *testing.T) { + value := util.Uint160{1, 2, 3} + addr := Address{ + IsSet: true, + Value: value, + } + + require.Equal(t, address.Uint160ToString(value), addr.String()) +} + +func TestAddress_Set(t *testing.T) { + value := util.Uint160{1, 2, 3} + addr := Address{} + + t.Run("bad address", func(t *testing.T) { + require.Error(t, addr.Set("not an address")) + }) + + t.Run("positive", func(t *testing.T) { + require.NoError(t, addr.Set(address.Uint160ToString(value))) + require.Equal(t, true, addr.IsSet) + require.Equal(t, value, addr.Value) + }) +} + +func TestAddress_Uint160(t *testing.T) { + value := util.Uint160{4, 5, 6} + addr := Address{} + + t.Run("not set", func(t *testing.T) { + require.Panics(t, func() { addr.Uint160() }) + }) + + t.Run("success", func(t *testing.T) { + addr.IsSet = true + addr.Value = value + require.Equal(t, value, addr.Uint160()) + }) +} + +func TestAddressFlag_IsSet(t *testing.T) { + flag := AddressFlag{} + + t.Run("not set", func(t *testing.T) { + require.False(t, flag.IsSet()) + }) + + t.Run("set", func(t *testing.T) { + flag.Value.IsSet = true + require.True(t, flag.IsSet()) + }) +} + +func TestAddressFlag_String(t *testing.T) { + flag := AddressFlag{ + Name: "myFlag", + Usage: "Address to pass", + Value: Address{}, + } + + require.Equal(t, "--myFlag value\tAddress to pass", flag.String()) +} + +func TestAddress_getNameHelp(t *testing.T) { + require.Equal(t, "-f value", getNameHelp("f")) + require.Equal(t, "--flag value", getNameHelp("flag")) +} + +func TestAddressFlag_Names(t *testing.T) { + flag := AddressFlag{ + Name: "flag", + Aliases: []string{"my"}, + } + + require.Equal(t, []string{"flag", "my"}, flag.Names()) +} + +func TestAddress(t *testing.T) { + f := flag.NewFlagSet("", flag.ContinueOnError) + f.SetOutput(io.Discard) // don't pollute test output + addr := AddressFlag{Name: "addr", Aliases: []string{"a"}} + err := addr.Apply(f) + require.NoError(t, err) + require.NoError(t, f.Parse([]string{"--addr", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"})) + require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String()) + require.NoError(t, f.Parse([]string{"-a", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"})) + require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String()) + require.Error(t, f.Parse([]string{"--addr", "kek"})) +} + +func TestAddressFlag_IsRequired(t *testing.T) { + flag := AddressFlag{Required: true} + require.True(t, flag.IsRequired()) + + flag.Required = false + require.False(t, flag.IsRequired()) +} + +func TestAddressFlag_IsVisible(t *testing.T) { + flag := AddressFlag{Hidden: false} + require.True(t, flag.IsVisible()) + + flag.Hidden = true + require.False(t, flag.IsVisible()) +} + +func TestAddressFlag_TakesValue(t *testing.T) { + flag := AddressFlag{} + require.True(t, flag.TakesValue()) +} + +func TestAddressFlag_GetUsage(t *testing.T) { + flag := AddressFlag{Usage: "Specify the address"} + require.Equal(t, "Specify the address", flag.GetUsage()) +} + +func TestAddressFlag_GetValue(t *testing.T) { + addrValue := util.Uint160{1, 2, 3} + flag := AddressFlag{Value: Address{IsSet: true, Value: addrValue}} + expectedStr := address.Uint160ToString(addrValue) + require.Equal(t, expectedStr, flag.GetValue()) +} + +func TestAddressFlag_Get(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + + flag := AddressFlag{ + Name: "testAddress", + Value: Address{Value: util.Uint160{1, 2, 3}, IsSet: false}, + } + + set.Var(&flag.Value, "testAddress", "test usage") + require.NoError(t, set.Set("testAddress", address.Uint160ToString(util.Uint160{3, 2, 1}))) + + expected := flag.Get(ctx) + require.True(t, expected.IsSet) + require.Equal(t, util.Uint160{3, 2, 1}, expected.Value) +} + +func TestAddressFlag_RunAction(t *testing.T) { + called := false + action := func(ctx *cli.Context, s string) error { + called = true + require.Equal(t, address.Uint160ToString(util.Uint160{1, 2, 3}), s) + return nil + } + + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + + flag := AddressFlag{ + Action: action, + Value: Address{IsSet: true, Value: util.Uint160{4, 5, 6}}, + } + + expected := address.Uint160ToString(util.Uint160{1, 2, 3}) + set.Var(&flag.Value, "testAddress", "test usage") + require.NoError(t, set.Set("testAddress", expected)) + require.Equal(t, expected, flag.GetValue()) + + err := flag.RunAction(ctx) + require.NoError(t, err) + require.True(t, called) +} diff --git a/cli/flags/fixed8.go b/cli/flags/fixed8.go new file mode 100644 index 0000000..7b7fd38 --- /dev/null +++ b/cli/flags/fixed8.go @@ -0,0 +1,123 @@ +package flags + +import ( + "flag" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/urfave/cli/v2" +) + +// Fixed8 is a wrapper for a Uint160 with flag.Value methods. +type Fixed8 struct { + Value fixedn.Fixed8 +} + +// Fixed8Flag is a flag with type string. +type Fixed8Flag struct { + Name string + Usage string + Value Fixed8 + Aliases []string + Required bool + Hidden bool + Action func(*cli.Context, string) error +} + +var ( + _ flag.Value = (*Fixed8)(nil) + _ cli.Flag = Fixed8Flag{} +) + +// String implements the fmt.Stringer interface. +func (a Fixed8) String() string { + return a.Value.String() +} + +// Set implements the flag.Value interface. +func (a *Fixed8) Set(s string) error { + f, err := fixedn.Fixed8FromString(s) + if err != nil { + return cli.Exit(err, 1) + } + a.Value = f + return nil +} + +// Fixed8 casts the address to util.Fixed8. +func (a *Fixed8) Fixed8() fixedn.Fixed8 { + return a.Value +} + +// IsSet checks if flag was set to a non-default value. +func (f Fixed8Flag) IsSet() bool { + return f.Value.Value != 0 +} + +// String returns a readable representation of this value +// (for usage defaults). +func (f Fixed8Flag) String() string { + var names []string + for _, name := range f.Names() { + names = append(names, getNameHelp(name)) + } + return strings.Join(names, ", ") + "\t" + f.Usage +} + +// Names returns the names of the flag. +func (f Fixed8Flag) Names() []string { + return cli.FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether the flag is required. +func (f Fixed8Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false. +func (f Fixed8Flag) IsVisible() bool { + return !f.Hidden +} + +// TakesValue returns true if the flag takes a value, otherwise false. +func (f Fixed8Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag. +func (f Fixed8Flag) GetUsage() string { + return f.Usage +} + +// Apply populates the flag given the flag set and environment. +// Ignores errors. +func (f Fixed8Flag) Apply(set *flag.FlagSet) error { + for _, name := range f.Names() { + set.Var(&f.Value, name, f.Usage) + } + return nil +} + +// Fixed8FromContext returns a parsed util.Fixed8 value provided flag name. +func Fixed8FromContext(ctx *cli.Context, name string) fixedn.Fixed8 { + return ctx.Generic(name).(*Fixed8).Value +} + +// RunAction executes flag action if set. +func (f Fixed8Flag) RunAction(c *cli.Context) error { + if f.Action != nil { + return f.Action(c, f.Value.Value.String()) + } + return nil +} + +// GetValue returns the flags value as string representation. +func (f Fixed8Flag) GetValue() string { + return f.Value.Value.String() +} + +// Get returns the flag’s value in the given Context. +func (f Fixed8Flag) Get(ctx *cli.Context) Fixed8 { + adr := ctx.Generic(f.Name).(*Fixed8) + return *adr +} diff --git a/cli/flags/fixed8_test.go b/cli/flags/fixed8_test.go new file mode 100644 index 0000000..d42a82b --- /dev/null +++ b/cli/flags/fixed8_test.go @@ -0,0 +1,128 @@ +package flags + +import ( + "flag" + "io" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func TestFixed8_String(t *testing.T) { + value := fixedn.Fixed8(123) + f := Fixed8{ + Value: value, + } + + require.Equal(t, "0.00000123", f.String()) +} + +func TestFixed8_Set(t *testing.T) { + value := fixedn.Fixed8(123) + f := Fixed8{} + + require.Error(t, f.Set("not-a-fixed8")) + + require.NoError(t, f.Set("0.00000123")) + require.Equal(t, value, f.Value) +} + +func TestFixed8_Fixed8(t *testing.T) { + f := Fixed8{ + Value: fixedn.Fixed8(123), + } + + require.Equal(t, fixedn.Fixed8(123), f.Fixed8()) +} + +func TestFixed8Flag_String(t *testing.T) { + flag := Fixed8Flag{ + Name: "myFlag", + Usage: "Gas amount", + } + + require.Equal(t, "--myFlag value\tGas amount", flag.String()) +} + +func TestFixed8Flag_Names(t *testing.T) { + flag := Fixed8Flag{ + Name: "myFlag", + } + + require.Equal(t, []string{"myFlag"}, flag.Names()) +} + +func TestFixed8(t *testing.T) { + f := flag.NewFlagSet("", flag.ContinueOnError) + f.SetOutput(io.Discard) // don't pollute test output + gas := Fixed8Flag{Name: "gas", Aliases: []string{"g"}, Usage: "Gas amount", Value: Fixed8{Value: 0}, Required: true, Hidden: false, Action: nil} + err := gas.Apply(f) + require.NoError(t, err) + require.NoError(t, f.Parse([]string{"--gas", "0.123"})) + require.Equal(t, "0.123", f.Lookup("g").Value.String()) + require.NoError(t, f.Parse([]string{"-g", "0.456"})) + require.Equal(t, "0.456", f.Lookup("g").Value.String()) + require.Error(t, f.Parse([]string{"--gas", "kek"})) +} + +func TestFixed8Flag_Get(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + flag := Fixed8Flag{ + Name: "testFlag", + } + fixedFlag := Fixed8{Value: fixedn.Fixed8(123)} + set.Var(&fixedFlag, "testFlag", "test usage") + require.NoError(t, set.Set("testFlag", "0.00000321")) + expected := flag.Get(ctx) + require.Equal(t, fixedn.Fixed8(321), expected.Value) +} + +func TestFixed8Flag_GetValue(t *testing.T) { + f := Fixed8Flag{Value: Fixed8{Value: fixedn.Fixed8(123)}} + require.Equal(t, "0.00000123", f.GetValue()) + require.True(t, f.TakesValue()) +} + +func TestFixed8Flag_RunAction(t *testing.T) { + called := false + action := func(ctx *cli.Context, s string) error { + called = true + require.Equal(t, "0.00000123", s) + return nil + } + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + f := Fixed8Flag{ + Action: action, + Value: Fixed8{Value: fixedn.Fixed8(123)}, + } + err := f.RunAction(ctx) + require.NoError(t, err) + require.True(t, called) +} + +func TestFixed8Flag_GetUsage(t *testing.T) { + f := Fixed8Flag{Usage: "Use this flag to specify gas amount"} + require.Equal(t, "Use this flag to specify gas amount", f.GetUsage()) +} + +func TestFixed8Flag_IsVisible(t *testing.T) { + f := Fixed8Flag{Hidden: false} + require.True(t, f.IsVisible()) + + f.Hidden = true + require.False(t, f.IsVisible()) +} + +func TestFixed8Flag_IsRequired(t *testing.T) { + f := Fixed8Flag{Required: false} + require.False(t, f.IsRequired()) + + f.Required = true + require.True(t, f.IsRequired()) +} diff --git a/cli/input/input.go b/cli/input/input.go new file mode 100644 index 0000000..43c6364 --- /dev/null +++ b/cli/input/input.go @@ -0,0 +1,71 @@ +package input + +import ( + "errors" + "fmt" + "io" + "os" + "syscall" + + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "golang.org/x/term" +) + +// Terminal is a terminal used for input. If `nil`, stdin is used. +var Terminal *term.Terminal + +// ReadWriter combiner reader and writer. +type ReadWriter struct { + io.Reader + io.Writer +} + +// ReadLine reads a line from the input without trailing '\n'. +func ReadLine(prompt string) (string, error) { + trm := Terminal + if trm == nil { + s, err := term.MakeRaw(int(syscall.Stdin)) + if err != nil { + return "", err + } + defer func() { _ = term.Restore(int(syscall.Stdin), s) }() + trm = term.NewTerminal(ReadWriter{ + Reader: os.Stdin, + Writer: os.Stdout, + }, "") + } + return readLine(trm, prompt) +} + +func readLine(trm *term.Terminal, prompt string) (string, error) { + _, err := trm.Write([]byte(prompt)) + if err != nil { + return "", err + } + return trm.ReadLine() +} + +// ReadPassword reads the user's password with prompt. +func ReadPassword(prompt string) (string, error) { + trm := Terminal + if trm != nil { + return trm.ReadPassword(prompt) + } + return readSecurePassword(prompt) +} + +// ConfirmTx asks for a confirmation to send the tx. +func ConfirmTx(w io.Writer, tx *transaction.Transaction) error { + fmt.Fprintf(w, "Network fee: %s\n", fixedn.Fixed8(tx.NetworkFee)) + fmt.Fprintf(w, "System fee: %s\n", fixedn.Fixed8(tx.SystemFee)) + fmt.Fprintf(w, "Total fee: %s\n", fixedn.Fixed8(tx.NetworkFee+tx.SystemFee)) + ln, err := ReadLine("Relay transaction (y|N)> ") + if err != nil { + return err + } + if 0 < len(ln) && (ln[0] == 'y' || ln[0] == 'Y') { + return nil + } + return errors.New("cancelled") +} diff --git a/cli/input/readpass_unix.go b/cli/input/readpass_unix.go new file mode 100644 index 0000000..c6f5f0d --- /dev/null +++ b/cli/input/readpass_unix.go @@ -0,0 +1,29 @@ +//go:build !windows + +package input + +import ( + "fmt" + "os" + + "golang.org/x/term" +) + +// readSecurePassword reads the user's password with prompt directly from /dev/tty. +func readSecurePassword(prompt string) (string, error) { + f, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return "", err + } + defer f.Close() + _, err = f.WriteString(prompt) + if err != nil { + return "", err + } + pass, err := term.ReadPassword(int(f.Fd())) + if err != nil { + return "", fmt.Errorf("failed to read password: %w", err) + } + _, err = f.WriteString("\n") + return string(pass), err +} diff --git a/cli/input/readpass_windows.go b/cli/input/readpass_windows.go new file mode 100644 index 0000000..1d3ed20 --- /dev/null +++ b/cli/input/readpass_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package input + +import ( + "os" + "syscall" + + "golang.org/x/term" +) + +// readSecurePassword reads the user's password with prompt. +func readSecurePassword(prompt string) (string, error) { + s, err := term.MakeRaw(int(syscall.Stdin)) + if err != nil { + return "", err + } + defer func() { _ = term.Restore(int(syscall.Stdin), s) }() + trm := term.NewTerminal(ReadWriter{os.Stdin, os.Stdout}, prompt) + return trm.ReadPassword(prompt) +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..bc07e14 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "os" + + "github.com/nspcc-dev/neo-go/cli/app" +) + +func main() { + ctl := app.New() + + if err := ctl.Run(os.Args); err != nil { + fmt.Fprintln(ctl.ErrWriter, err) + os.Exit(1) + } +} diff --git a/cli/nep_test/nep11_test.go b/cli/nep_test/nep11_test.go new file mode 100644 index 0000000..8f65583 --- /dev/null +++ b/cli/nep_test/nep11_test.go @@ -0,0 +1,682 @@ +package nep_test + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "math/big" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/internal/versionutil" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +const ( + // nftOwnerAddr is the owner of NFT-ND HASHY token (../examples/nft-nd/nft.go). + nftOwnerAddr = "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB" + nftOwnerWallet = "../../examples/my_wallet.json" + nftOwnerPass = "qwerty" + + // Keep contract NEFs consistent between runs. + _ = versionutil.TestVersion +) + +func TestNEP11Import(t *testing.T) { + e := testcli.NewExecutor(t, true) + + tmpDir := t.TempDir() + walletPath := filepath.Join(tmpDir, "walletForImport.json") + + // deploy NFT NeoNameService contract + nnsContractHash := deployNNSContract(t, e) + // deploy NFT-D NeoFS Object contract + nfsContractHash := deployNFSContract(t, e) + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) + require.NoError(t, err) + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) + + args := []string{ + "neo-go", "wallet", "nep11", "import", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", walletPath, + } + // missing token hash + e.RunWithErrorCheck(t, `Required flag "token" not set`, args...) + + // excessive parameters + e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE(), "something")...) + + // empty token hash + e.RunWithErrorCheck(t, `invalid value "" for flag -token: zero length string`, append(args, "--token", "")...) + + // good: non-divisible + e.Run(t, append(args, "--token", nnsContractHash.StringLE())...) + + // good: divisible + e.Run(t, append(args, "--token", nfsContractHash.StringLE())...) + + // already exists + e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...) + + // not a NEP-11 token + e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...) + + checkInfo := func(t *testing.T, h util.Uint160, name string, symbol string, decimals int) { + e.CheckNextLine(t, "^Name:\\s*"+name) + e.CheckNextLine(t, "^Symbol:\\s*"+symbol) + e.CheckNextLine(t, "^Hash:\\s*"+h.StringLE()) + e.CheckNextLine(t, "^Decimals:\\s*"+strconv.Itoa(decimals)) + e.CheckNextLine(t, "^Address:\\s*"+address.Uint160ToString(h)) + e.CheckNextLine(t, "^Standard:\\s*"+string(manifest.NEP11StandardName)) + } + t.Run("Info", func(t *testing.T) { + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "nep11", "info", + "--wallet", walletPath, "--token", nnsContractHash.StringLE(), "qwerty") + }) + t.Run("WithToken", func(t *testing.T) { + e.Run(t, "neo-go", "wallet", "nep11", "info", + "--wallet", walletPath, "--token", nnsContractHash.StringLE()) + checkInfo(t, nnsContractHash, "NameService", "NNS", 0) + }) + t.Run("NoToken", func(t *testing.T) { + e.Run(t, "neo-go", "wallet", "nep11", "info", + "--wallet", walletPath) + checkInfo(t, nnsContractHash, "NameService", "NNS", 0) + e.CheckNextLine(t, "") + checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2) + }) + }) + + t.Run("Remove", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "nep11", "remove", + "--wallet", walletPath, "--token", nnsContractHash.StringLE(), "parameter") + e.In.WriteString("y\r") + e.Run(t, "neo-go", "wallet", "nep11", "remove", + "--wallet", walletPath, "--token", nnsContractHash.StringLE()) + e.Run(t, "neo-go", "wallet", "nep11", "info", + "--wallet", walletPath) + checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2) + _, err := e.Out.ReadString('\n') + require.Equal(t, err, io.EOF) + }) +} + +func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + + // copy wallet to temp dir in order not to overwrite the original file + bytesRead, err := os.ReadFile(nftOwnerWallet) + require.NoError(t, err) + wall := filepath.Join(tmpDir, "my_wallet.json") + err = os.WriteFile(wall, bytesRead, 0755) + require.NoError(t, err) + + // transfer funds to contract owner + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--to", nftOwnerAddr, + "--token", "GAS", + "--amount", "10000", + "--force", + "--from", testcli.ValidatorAddr) + e.CheckTxPersisted(t) + + // deploy NFT HASHY contract + h := deployNFTContract(t, e) + + mint := func(t *testing.T) []byte { + // mint 1 HASHY token by transferring 10 GAS to HASHY contract + e.In.WriteString(nftOwnerPass + "\r") + e.Run(t, "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", wall, + "--to", h.StringLE(), + "--token", "GAS", + "--amount", "10", + "--force", + "--from", nftOwnerAddr) + txMint, _ := e.CheckTxPersisted(t) + + // get NFT ID from AER + aer, err := e.Chain.GetAppExecResults(txMint.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, 1, len(aer)) + require.Equal(t, 2, len(aer[0].Events)) + hashyMintEvent := aer[0].Events[1] + require.Equal(t, "Transfer", hashyMintEvent.Name) + tokenID, err := hashyMintEvent.Item.Value().([]stackitem.Item)[3].TryBytes() + require.NoError(t, err) + require.NotNil(t, tokenID) + return tokenID + } + + tokenID := mint(t) + var hashBeforeTransfer = e.Chain.CurrentHeaderHash() + + // check the balance + cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", wall, + "--address", nftOwnerAddr} + checkBalanceResult := func(t *testing.T, acc string, ids ...[]byte) { + e.CheckNextLine(t, "^\\s*Account\\s+"+acc) + e.CheckNextLine(t, "^\\s*HASHY:\\s+HASHY NFT \\("+h.StringLE()+"\\)") + + // Hashes can be ordered in any way, so make a regexp for them. + var tokstring strings.Builder + tokstring.WriteString("(") + for i, id := range ids { + if i > 0 { + tokstring.WriteString("|") + } + tokstring.WriteString(hex.EncodeToString(id)) + } + tokstring.WriteString(")") + + for range ids { + e.CheckNextLine(t, "^\\s*Token: "+tokstring.String()+"\\s*$") + e.CheckNextLine(t, "^\\s*Amount: 1\\s*$") + e.CheckNextLine(t, "^\\s*Updated: [0-9]+\\s*$") + } + e.CheckEOF(t) + } + // balance check: by symbol, token is not imported + e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...) + checkBalanceResult(t, nftOwnerAddr, tokenID) + // balance check: excessive parameters + e.RunWithError(t, append(cmdCheckBalance, "--token", h.StringLE(), "neo-go")...) + // balance check: by hash, ok + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, nftOwnerAddr, tokenID) + + // import token + e.Run(t, "neo-go", "wallet", "nep11", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", wall, + "--token", h.StringLE()) + + // balance check: by symbol, ok + e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...) + checkBalanceResult(t, nftOwnerAddr, tokenID) + + // balance check: all accounts + e.Run(t, "neo-go", "wallet", "nep11", "balance", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", wall, + "--token", h.StringLE()) + checkBalanceResult(t, nftOwnerAddr, tokenID) + + // remove token from wallet + e.In.WriteString("y\r") + e.Run(t, "neo-go", "wallet", "nep11", "remove", + "--wallet", wall, "--token", h.StringLE()) + + // ownerOf: missing contract hash + cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOf", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...) + cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE()) + + // ownerOf: missing token ID + e.RunWithError(t, cmdOwnerOf...) + cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(tokenID)) + + // ownerOf: good + e.Run(t, cmdOwnerOf...) + e.CheckNextLine(t, nftOwnerAddr) + + // tokensOf: missing contract hash + cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...) + cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE()) + + // tokensOf: missing owner address + e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...) + cmdTokensOf = append(cmdTokensOf, "--address", nftOwnerAddr) + + // tokensOf: good + e.Run(t, cmdTokensOf...) + require.Equal(t, hex.EncodeToString(tokenID), e.GetNextLine(t)) + + // properties: no contract + cmdProperties := []string{ + "neo-go", "wallet", "nep11", "properties", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...) + cmdProperties = append(cmdProperties, "--token", h.StringLE()) + + // properties: no token ID + e.RunWithError(t, cmdProperties...) + cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(tokenID)) + + // properties: ok + e.Run(t, cmdProperties...) + require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.GetNextLine(t)) + + // tokensOf: good, several tokens + tokenID1 := mint(t) + e.Run(t, cmdTokensOf...) + fst, snd := tokenID, tokenID1 + if bytes.Compare(tokenID, tokenID1) == 1 { + fst, snd = snd, fst + } + + require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t)) + require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t)) + + // tokens: missing contract hash + cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...) + cmdTokens = append(cmdTokens, "--token", h.StringLE()) + + // tokens: excessive parameters + e.RunWithError(t, append(cmdTokens, "additional")...) + // tokens: good, several tokens + e.Run(t, cmdTokens...) + require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t)) + require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t)) + + // balance check: several tokens, ok + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, nftOwnerAddr, tokenID, tokenID1) + + cmdTransfer := []string{ + "neo-go", "wallet", "nep11", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", wall, + "--to", testcli.ValidatorAddr, + "--from", nftOwnerAddr, + "--force", + } + + // transfer: unimported token with symbol id specified + e.In.WriteString(nftOwnerPass + "\r") + e.RunWithError(t, append(cmdTransfer, + "--token", "HASHY")...) + cmdTransfer = append(cmdTransfer, "--token", h.StringLE()) + + // transfer: no id specified + e.In.WriteString(nftOwnerPass + "\r") + e.RunWithError(t, cmdTransfer...) + + // transfer: good + e.In.WriteString(nftOwnerPass + "\r") + e.Run(t, append(cmdTransfer, "--id", hex.EncodeToString(tokenID))...) + e.CheckTxPersisted(t) + + // check balance after transfer + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, nftOwnerAddr, tokenID1) + + // check --await flag + tokenID2 := mint(t) + e.In.WriteString(nftOwnerPass + "\r") + e.Run(t, append(cmdTransfer, "--await", "--id", hex.EncodeToString(tokenID2))...) + e.CheckAwaitableTxPersisted(t) + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, nftOwnerAddr, tokenID1) + + // transfer: good, to NEP-11-Payable contract, with data + verifyH := deployVerifyContract(t, e) + cmdTransfer = []string{ + "neo-go", "wallet", "nep11", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", wall, + "--to", verifyH.StringLE(), + "--from", nftOwnerAddr, + "--token", h.StringLE(), + "--id", hex.EncodeToString(tokenID1), + "--force", + "string:some_data", + } + e.In.WriteString(nftOwnerPass + "\r") + e.Run(t, cmdTransfer...) + tx, _ := e.CheckTxPersisted(t) + // check OnNEP11Payment event + aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, 2, len(aer[0].Events)) + nftOwnerHash, err := address.StringToUint160(nftOwnerAddr) + require.NoError(t, err) + require.Equal(t, state.NotificationEvent{ + ScriptHash: verifyH, + Name: "OnNEP11Payment", + Item: stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(nftOwnerHash.BytesBE()), + stackitem.NewBigInteger(big.NewInt(1)), + stackitem.NewByteArray(tokenID1), + stackitem.NewByteArray([]byte("some_data")), + }), + }, aer[0].Events[1]) + + // check balance after transfer + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, nftOwnerAddr) + + // historic calls still remember the good old days. + cmdOwnerOf = append(cmdOwnerOf, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdOwnerOf...) + e.CheckNextLine(t, nftOwnerAddr) + + cmdTokensOf = append(cmdTokensOf, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdTokensOf...) + require.Equal(t, hex.EncodeToString(tokenID), e.GetNextLine(t)) + + cmdTokens = append(cmdTokens, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdTokens...) + require.Equal(t, hex.EncodeToString(tokenID), e.GetNextLine(t)) + + // this one is not affected by transfer, but anyway + cmdProperties = append(cmdProperties, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdProperties...) + require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.GetNextLine(t)) +} + +func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + + // copy wallet to temp dir in order not to overwrite the original file + bytesRead, err := os.ReadFile(testcli.ValidatorWallet) + require.NoError(t, err) + wall := filepath.Join(tmpDir, "my_wallet.json") + err = os.WriteFile(wall, bytesRead, 0755) + require.NoError(t, err) + + // deploy NeoFS Object contract + h := deployNFSContract(t, e) + + mint := func(t *testing.T, containerID, objectID util.Uint256) []byte { + // mint 1.00 NFSO token by transferring 10 GAS to NFSO contract + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", wall, + "--to", h.StringLE(), + "--token", "GAS", + "--amount", "10", + "--force", + "--from", testcli.ValidatorAddr, + "--", "[", "hash256:"+containerID.StringLE(), "hash256:"+objectID.StringLE(), "]", + ) + txMint, _ := e.CheckTxPersisted(t) + + // get NFT ID from AER + aer, err := e.Chain.GetAppExecResults(txMint.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, 1, len(aer)) + require.Equal(t, 2, len(aer[0].Events)) + nfsoMintEvent := aer[0].Events[1] + require.Equal(t, "Transfer", nfsoMintEvent.Name) + tokenID, err := nfsoMintEvent.Item.Value().([]stackitem.Item)[3].TryBytes() + require.NoError(t, err) + require.NotNil(t, tokenID) + return tokenID + } + + container1ID := util.Uint256{1, 2, 3} + object1ID := util.Uint256{4, 5, 6} + token1ID := mint(t, container1ID, object1ID) + + container2ID := util.Uint256{7, 8, 9} + object2ID := util.Uint256{10, 11, 12} + token2ID := mint(t, container2ID, object2ID) + + // check properties + e.Run(t, "neo-go", "wallet", "nep11", "properties", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--token", h.StringLE(), + "--id", hex.EncodeToString(token1ID)) + jProps := e.GetNextLine(t) + props := make(map[string]string) + require.NoError(t, json.Unmarshal([]byte(jProps), &props)) + require.Equal(t, base64.StdEncoding.EncodeToString(container1ID.BytesBE()), props["containerID"]) + require.Equal(t, base64.StdEncoding.EncodeToString(object1ID.BytesBE()), props["objectID"]) + e.CheckEOF(t) + + type idAmount struct { + id string + amount string + } + + // check the balance + cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", wall, + "--address", testcli.ValidatorAddr} + checkBalanceResult := func(t *testing.T, acc string, objs ...idAmount) { + e.CheckNextLine(t, "^\\s*Account\\s+"+acc) + e.CheckNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)") + + for _, o := range objs { + e.CheckNextLine(t, "^\\s*Token: "+o.id+"\\s*$") + e.CheckNextLine(t, "^\\s*Amount: "+o.amount+"\\s*$") + e.CheckNextLine(t, "^\\s*Updated: [0-9]+\\s*$") + } + e.CheckEOF(t) + } + tokz := []idAmount{ + {hex.EncodeToString(token1ID), "1"}, + {hex.EncodeToString(token2ID), "1"}, + } + // balance check: by symbol, token is not imported + e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz...) + + // overall NFSO balance check: by hash, ok + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz...) + + // particular NFSO balance check: by hash, ok + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE(), "--id", hex.EncodeToString(token2ID))...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz[1]) + + // import token + e.Run(t, "neo-go", "wallet", "nep11", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", wall, + "--token", h.StringLE()) + + // overall balance check: by symbol, ok + e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz...) + + // particular balance check: by symbol, ok + e.Run(t, append(cmdCheckBalance, "--token", "NFSO", "--id", hex.EncodeToString(token1ID))...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz[0]) + + // remove token from wallet + e.In.WriteString("y\r") + e.Run(t, "neo-go", "wallet", "nep11", "remove", + "--wallet", wall, "--token", h.StringLE()) + + // ownerOfD: missing contract hash + cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...) + cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE()) + + // ownerOfD: missing token ID + e.RunWithError(t, cmdOwnerOf...) + cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(token1ID)) + + // ownerOfD: good + e.Run(t, cmdOwnerOf...) + e.CheckNextLine(t, testcli.ValidatorAddr) + + // tokensOf: missing contract hash + cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...) + cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE()) + + // tokensOf: missing owner address + e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...) + cmdTokensOf = append(cmdTokensOf, "--address", testcli.ValidatorAddr) + + // tokensOf: good + e.Run(t, cmdTokensOf...) + require.Equal(t, hex.EncodeToString(token1ID), e.GetNextLine(t)) + require.Equal(t, hex.EncodeToString(token2ID), e.GetNextLine(t)) + e.CheckEOF(t) + + // properties: no contract + cmdProperties := []string{ + "neo-go", "wallet", "nep11", "properties", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...) + cmdProperties = append(cmdProperties, "--token", h.StringLE()) + + // properties: no token ID + e.RunWithError(t, cmdProperties...) + cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(token2ID)) + + // properties: additional parameter + e.RunWithError(t, append(cmdProperties, "additiona")...) + + // properties: ok + e.Run(t, cmdProperties...) + jProps = e.GetNextLine(t) + props = make(map[string]string) + require.NoError(t, json.Unmarshal([]byte(jProps), &props)) + require.Equal(t, base64.StdEncoding.EncodeToString(container2ID.BytesBE()), props["containerID"]) + require.Equal(t, base64.StdEncoding.EncodeToString(object2ID.BytesBE()), props["objectID"]) + e.CheckEOF(t) + + // tokensOf: good, several tokens + e.Run(t, cmdTokensOf...) + fst, snd := token1ID, token2ID + if bytes.Compare(token1ID, token2ID) == 1 { + fst, snd = snd, fst + } + + require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t)) + require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t)) + + // tokens: missing contract hash + cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + } + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...) + cmdTokens = append(cmdTokens, "--token", h.StringLE()) + + // tokens: good, several tokens + e.Run(t, cmdTokens...) + require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t)) + require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t)) + + // balance check: several tokens, ok + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz...) + + cmdTransfer := []string{ + "neo-go", "wallet", "nep11", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", wall, + "--to", nftOwnerAddr, + "--from", testcli.ValidatorAddr, + "--force", + } + + // transfer: unimported token with symbol id specified + e.In.WriteString(testcli.ValidatorPass + "\r") + e.RunWithError(t, append(cmdTransfer, + "--token", "NFSO")...) + cmdTransfer = append(cmdTransfer, "--token", h.StringLE()) + + // transfer: no id specified + e.In.WriteString(testcli.ValidatorPass + "\r") + e.RunWithError(t, cmdTransfer...) + + // transfer: good + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, append(cmdTransfer, "--id", hex.EncodeToString(token1ID))...) + e.CheckTxPersisted(t) + + // check balance after transfer + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, testcli.ValidatorAddr, tokz[1]) // only token2ID expected to be on the balance + + // transfer: good, 1/4 of the balance, to NEP-11-Payable contract, with data + verifyH := deployVerifyContract(t, e) + cmdTransfer = []string{ + "neo-go", "wallet", "nep11", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", wall, + "--to", verifyH.StringLE(), + "--from", testcli.ValidatorAddr, + "--token", h.StringLE(), + "--id", hex.EncodeToString(token2ID), + "--amount", "0.25", + "--force", + "string:some_data", + } + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, cmdTransfer...) + tx, _ := e.CheckTxPersisted(t) + // check OnNEP11Payment event + aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, 2, len(aer[0].Events)) + validatorHash, err := address.StringToUint160(testcli.ValidatorAddr) + require.NoError(t, err) + require.Equal(t, state.NotificationEvent{ + ScriptHash: verifyH, + Name: "OnNEP11Payment", + Item: stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(validatorHash.BytesBE()), + stackitem.NewBigInteger(big.NewInt(25)), + stackitem.NewByteArray(token2ID), + stackitem.NewByteArray([]byte("some_data")), + }), + }, aer[0].Events[1]) + + // check balance after transfer + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + tokz[1].amount = "0.75" + checkBalanceResult(t, testcli.ValidatorAddr, tokz[1]) +} + +func deployNFSContract(t *testing.T, e *testcli.Executor) util.Uint160 { + return testcli.DeployContract(t, e, "../../examples/nft-d/nft.go", "../../examples/nft-d/nft.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass) +} + +func deployNFTContract(t *testing.T, e *testcli.Executor) util.Uint160 { + return testcli.DeployContract(t, e, "../../examples/nft-nd/nft.go", "../../examples/nft-nd/nft.yml", nftOwnerWallet, nftOwnerAddr, nftOwnerPass) +} + +func deployNNSContract(t *testing.T, e *testcli.Executor) util.Uint160 { + return testcli.DeployContract(t, e, "../../examples/nft-nd-nns/", "../../examples/nft-nd-nns/nns.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass) +} + +func deployVerifyContract(t *testing.T, e *testcli.Executor) util.Uint160 { + return testcli.DeployContract(t, e, "../smartcontract/testdata/verify.go", "../smartcontract/testdata/verify.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass) +} diff --git a/cli/nep_test/nep17_test.go b/cli/nep_test/nep17_test.go new file mode 100644 index 0000000..a9340b3 --- /dev/null +++ b/cli/nep_test/nep17_test.go @@ -0,0 +1,435 @@ +package nep_test + +import ( + "io" + "math/big" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/require" +) + +func TestNEP17Balance(t *testing.T) { + e := testcli.NewExecutor(t, true) + + args := []string{ + "neo-go", "wallet", "nep17", "multitransfer", "--force", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--from", testcli.ValidatorAddr, + "GAS:" + testcli.TestWalletMultiAccount1 + ":1", + "NEO:" + testcli.TestWalletMultiAccount1 + ":10", + "GAS:" + testcli.TestWalletMultiAccount3 + ":3", + } + e.In.WriteString("one\r") + e.Run(t, args...) + e.CheckTxPersisted(t) + + var checkAcc1NEO = func(t *testing.T, e *testcli.Executor, line string) { + if line == "" { + line = e.GetNextLine(t) + } + balance, index := e.Chain.GetGoverningTokenBalance(testcli.TestWalletMultiAccount1Hash) + e.CheckLine(t, line, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") + e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+balance.String()+"$") + e.CheckNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) + } + var checkAcc1GAS = func(t *testing.T, e *testcli.Executor, line string) { + if line == "" { + line = e.GetNextLine(t) + } + e.CheckLine(t, line, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + balance := e.Chain.GetUtilityTokenBalance(testcli.TestWalletMultiAccount1Hash) + e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()+"$") + e.CheckNextLine(t, "^\\s*Updated:") + } + var checkAcc1Assets = func(t *testing.T, e *testcli.Executor) { + e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount1) + // The order of assets is undefined. + for range 2 { + line := e.GetNextLine(t) + if strings.Contains(line, "GAS") { + checkAcc1GAS(t, e, line) + } else { + checkAcc1NEO(t, e, line) + } + } + } + + var ( + cmdbase = []string{"neo-go", "wallet", "nep17", "balance", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} + addrparams = []string{"--address", testcli.TestWalletMultiAccount1} + walletparams = []string{"--wallet", testcli.TestWalletMultiPath} + ) + t.Run("Bad wallet", func(t *testing.T) { + e.RunWithError(t, append(cmdbase, "--wallet", "/dev/null")...) + }) + t.Run("empty wallet", func(t *testing.T) { + tmpDir := t.TempDir() + walletPath := filepath.Join(tmpDir, "emptywallet.json") + require.NoError(t, os.WriteFile(walletPath, []byte("{}"), 0o644)) + e.RunWithError(t, append(cmdbase, "--wallet", walletPath)...) + }) + t.Run("no wallet or address", func(t *testing.T) { + e.RunWithError(t, cmdbase...) + }) + for name, params := range map[string][]string{ + "address only": addrparams, + "address with wallet": slices.Concat(walletparams, addrparams), + } { + var cmd = append(cmdbase, params...) + t.Run(name, func(t *testing.T) { + t.Run("all tokens", func(t *testing.T) { + e.Run(t, cmd...) + checkAcc1Assets(t, e) + e.CheckEOF(t) + }) + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--token", "NEO", "gas")...) + }) + }) + t.Run("NEO", func(t *testing.T) { + checkResult := func(t *testing.T) { + e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1) + checkAcc1NEO(t, e, "") + e.CheckEOF(t) + } + t.Run("Alias", func(t *testing.T) { + e.Run(t, append(cmd, "--token", "NEO")...) + checkResult(t) + }) + t.Run("Hash", func(t *testing.T) { + e.Run(t, append(cmd, "--token", e.Chain.GoverningTokenHash().StringLE())...) + checkResult(t) + }) + }) + t.Run("GAS", func(t *testing.T) { + e.Run(t, append(cmd, "--token", "GAS")...) + e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1) + checkAcc1GAS(t, e, "") + }) + t.Run("Bad token", func(t *testing.T) { + e.Run(t, append(cmd, "--token", "kek")...) + e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1) + e.CheckNextLine(t, `^\s*Can't find data for "kek" token\s*`) + e.CheckEOF(t) + }) + } + t.Run("inexistent wallet account", func(t *testing.T) { + var cmd = append(cmdbase, walletparams...) + e.RunWithError(t, append(cmd, "--address", "NSPCCpw8YmgNDYWiBfXJHRfz38NDjv6WW3")...) + }) + t.Run("zero balance of known token", func(t *testing.T) { + e.Run(t, append(cmdbase, []string{"--token", "NEO", "--address", testcli.TestWalletMultiAccount2}...)...) + e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount2) + e.CheckNextLine(t, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") + e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(0).String()+"$") + e.CheckNextLine(t, "^\\s*Updated:") + e.CheckEOF(t) + }) + t.Run("all accounts", func(t *testing.T) { + e.Run(t, append(cmdbase, walletparams...)...) + + checkAcc1Assets(t, e) + e.CheckNextLine(t, "^\\s*$") + + e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount2) + e.CheckNextLine(t, "^\\s*$") + + e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount3) + e.CheckNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + balance := e.Chain.GetUtilityTokenBalance(testcli.TestWalletMultiAccount3Hash) + e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()+"$") + e.CheckNextLine(t, "^\\s*Updated:") + e.CheckEOF(t) + }) +} + +func TestNEP17Transfer(t *testing.T) { + w, err := wallet.NewWalletFromFile("../testdata/testwallet.json") + require.NoError(t, err) + + e := testcli.NewExecutor(t, true) + args := []string{ + "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--to", w.Accounts[0].Address, + "--token", "NEO", + "--amount", "1", + "--from", testcli.ValidatorAddr, + } + + t.Run("missing receiver", func(t *testing.T) { + as := slices.Concat(args[:8], args[10:]) + e.In.WriteString("one\r") + e.RunWithErrorCheck(t, `Required flag "to" not set`, as...) + e.In.Reset() + }) + + t.Run("InvalidPassword", func(t *testing.T) { + e.In.WriteString("onetwothree\r") + e.RunWithError(t, args...) + e.In.Reset() + }) + + t.Run("no confirmation", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, args...) + e.In.Reset() + }) + t.Run("cancel after prompt", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, args...) + e.In.Reset() + }) + + e.In.WriteString("one\r") + e.In.WriteString("Y\r") + e.Run(t, args...) + e.CheckNextLine(t, `^Network fee:\s*(\d|\.)+`) + e.CheckNextLine(t, `^System fee:\s*(\d|\.)+`) + e.CheckNextLine(t, `^Total fee:\s*(\d|\.)+`) + e.CheckTxPersisted(t) + + sh := w.Accounts[0].ScriptHash() + b, _ := e.Chain.GetGoverningTokenBalance(sh) + require.Equal(t, big.NewInt(1), b) + + t.Run("with force", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(args, "--force")...) + e.CheckTxPersisted(t) + + b, _ := e.Chain.GetGoverningTokenBalance(sh) + require.Equal(t, big.NewInt(2), b) + }) + + hVerify := deployVerifyContract(t, e) + const validatorDefault = "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn" + + t.Run("default address", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--from", testcli.ValidatorAddr, + "--force", + "NEO:"+validatorDefault+":42", + "GAS:"+validatorDefault+":7") + e.CheckTxPersisted(t) + + args := args[:len(args)-2] // cut '--from' argument + args = append(args, "--force") + e.In.WriteString("one\r") + e.Run(t, args...) + e.CheckTxPersisted(t) + + b, _ := e.Chain.GetGoverningTokenBalance(sh) + require.Equal(t, big.NewInt(3), b) + + sh, err = address.StringToUint160(validatorDefault) + require.NoError(t, err) + b, _ = e.Chain.GetGoverningTokenBalance(sh) + require.Equal(t, big.NewInt(41), b) + }) + + t.Run("with signers", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--from", testcli.ValidatorAddr, + "--force", + "NEO:"+validatorDefault+":42", + "GAS:"+validatorDefault+":7", + "--", testcli.ValidatorAddr+":Global") + e.CheckTxPersisted(t) + }) + + validTil := e.Chain.BlockHeight() + 100 + cmd := []string{ + "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--token", "GAS", + "--amount", "1", + "--force", + "--from", testcli.ValidatorAddr} + + t.Run("with await", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--to", nftOwnerAddr, "--await")...) + e.CheckAwaitableTxPersisted(t) + }) + + cmd = append(cmd, "--to", address.Uint160ToString(nativehashes.Notary), + "[", testcli.ValidatorAddr, strconv.Itoa(int(validTil)), "]") + + t.Run("with data", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, cmd...) + e.CheckTxPersisted(t) + }) + + t.Run("with data and signers", func(t *testing.T) { + t.Run("invalid sender's scope", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, append(cmd, "--", testcli.ValidatorAddr+":None")...) + }) + t.Run("good", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--", testcli.ValidatorAddr+":Global")...) // CalledByEntry is enough, but it's the default value, so check something else + e.CheckTxPersisted(t) + }) + t.Run("several signers", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--", testcli.ValidatorAddr, hVerify.StringLE())...) + e.CheckTxPersisted(t) + }) + }) +} + +func TestNEP17MultiTransfer(t *testing.T) { + privs, _ := testcli.GenerateKeys(t, 3) + + e := testcli.NewExecutor(t, true) + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) + require.NoError(t, err) + args := []string{ + "neo-go", "wallet", "nep17", "multitransfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--from", testcli.ValidatorAddr, + "--force", + "NEO:" + privs[0].Address() + ":42", + "GAS:" + privs[1].Address() + ":7", + neoContractHash.StringLE() + ":" + privs[2].Address() + ":13", + } + hVerify := deployVerifyContract(t, e) + + t.Run("no cosigners", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, args...) + e.CheckTxPersisted(t) + + b, _ := e.Chain.GetGoverningTokenBalance(privs[0].GetScriptHash()) + require.Equal(t, big.NewInt(42), b) + b = e.Chain.GetUtilityTokenBalance(privs[1].GetScriptHash()) + require.Equal(t, big.NewInt(int64(fixedn.Fixed8FromInt64(7))), b) + b, _ = e.Chain.GetGoverningTokenBalance(privs[2].GetScriptHash()) + require.Equal(t, big.NewInt(13), b) + }) + + t.Run("invalid sender scope", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, append(args, + "--", testcli.ValidatorAddr+":None")...) // invalid sender scope + }) + t.Run("Global sender scope", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(args, + "--", testcli.ValidatorAddr+":Global")...) + e.CheckTxPersisted(t) + }) + t.Run("Several cosigners", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(args, + "--", testcli.ValidatorAddr, hVerify.StringLE())...) + e.CheckTxPersisted(t) + }) +} + +func TestNEP17ImportToken(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + walletPath := filepath.Join(tmpDir, "walletForImport.json") + + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) + require.NoError(t, err) + gasContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas) + require.NoError(t, err) + nnsContractHash := deployNNSContract(t, e) + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) + + // missing token hash + e.RunWithErrorCheck(t, `Required flag "token" not set`, "neo-go", "wallet", "nep17", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", walletPath) + + // additional parameter + e.RunWithError(t, "neo-go", "wallet", "nep17", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", walletPath, + "--token", gasContractHash.StringLE(), "useless") + e.Run(t, "neo-go", "wallet", "nep17", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", walletPath, + "--token", gasContractHash.StringLE()) + e.Run(t, "neo-go", "wallet", "nep17", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", walletPath, + "--token", address.Uint160ToString(neoContractHash)) // try address instead of sh + + // not a NEP-17 token + e.RunWithError(t, "neo-go", "wallet", "nep17", "import", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", walletPath, + "--token", nnsContractHash.StringLE()) + + t.Run("Info", func(t *testing.T) { + checkGASInfo := func(t *testing.T) { + e.CheckNextLine(t, "^Name:\\s*GasToken") + e.CheckNextLine(t, "^Symbol:\\s*GAS") + e.CheckNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE()) + e.CheckNextLine(t, "^Decimals:\\s*8") + e.CheckNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash)) + e.CheckNextLine(t, "^Standard:\\s*"+string(manifest.NEP17StandardName)) + } + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "nep17", "info", + "--wallet", walletPath, "--token", gasContractHash.StringLE(), "parameter") + }) + t.Run("WithToken", func(t *testing.T) { + e.Run(t, "neo-go", "wallet", "nep17", "info", + "--wallet", walletPath, "--token", gasContractHash.StringLE()) + checkGASInfo(t) + }) + t.Run("NoToken", func(t *testing.T) { + e.Run(t, "neo-go", "wallet", "nep17", "info", + "--wallet", walletPath) + checkGASInfo(t) + _, err := e.Out.ReadString('\n') + require.NoError(t, err) + e.CheckNextLine(t, "^Name:\\s*NeoToken") + e.CheckNextLine(t, "^Symbol:\\s*NEO") + e.CheckNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE()) + e.CheckNextLine(t, "^Decimals:\\s*0") + e.CheckNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash)) + e.CheckNextLine(t, "^Standard:\\s*"+string(manifest.NEP17StandardName)) + }) + t.Run("Remove", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "nep17", "remove", + "--wallet", walletPath, "--token", neoContractHash.StringLE(), "add") + e.In.WriteString("y\r") + e.Run(t, "neo-go", "wallet", "nep17", "remove", + "--wallet", walletPath, "--token", neoContractHash.StringLE()) + e.Run(t, "neo-go", "wallet", "nep17", "info", + "--wallet", walletPath) + checkGASInfo(t) + _, err := e.Out.ReadString('\n') + require.Equal(t, err, io.EOF) + }) + }) +} diff --git a/cli/options/cli_options_test.go b/cli/options/cli_options_test.go new file mode 100644 index 0000000..4cf9977 --- /dev/null +++ b/cli/options/cli_options_test.go @@ -0,0 +1,33 @@ +package options_test + +import ( + "flag" + "testing" + + "github.com/nspcc-dev/neo-go/cli/app" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func TestGetRPCClient(t *testing.T) { + e := testcli.NewExecutor(t, true) + + t.Run("no endpoint", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + ctx := cli.NewContext(app.New(), set, nil) + gctx, _ := options.GetTimeoutContext(ctx) + _, ec := options.GetRPCClient(gctx, ctx) + require.Equal(t, 1, ec.ExitCode()) + }) + + t.Run("success", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addresses()[0], "") + ctx := cli.NewContext(app.New(), set, nil) + gctx, _ := options.GetTimeoutContext(ctx) + _, ec := options.GetRPCClient(gctx, ctx) + require.Nil(t, ec) + }) +} diff --git a/cli/options/filtering_core.go b/cli/options/filtering_core.go new file mode 100644 index 0000000..4dd0ad2 --- /dev/null +++ b/cli/options/filtering_core.go @@ -0,0 +1,28 @@ +package options + +import "go.uber.org/zap/zapcore" + +// FilteringCore is custom implementation of zapcore.Core that allows to filter +// log entries using custom filtering function. +type FilteringCore struct { + zapcore.Core + filter FilterFunc +} + +// FilterFunc is the filter function that is called to check whether the given +// entry together with the associated fields is to be written to a core or not. +type FilterFunc func(zapcore.Entry) bool + +// NewFilteringCore returns a core middleware that uses the given filter function +// to decide whether to log this message or not. +func NewFilteringCore(next zapcore.Core, filter FilterFunc) zapcore.Core { + return &FilteringCore{next, filter} +} + +// Check implements zapcore.Core interface and performs log entries filtering. +func (c *FilteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if c.filter(e) { + return c.Core.Check(e, ce) + } + return ce +} diff --git a/cli/options/options.go b/cli/options/options.go new file mode 100644 index 0000000..7a084a5 --- /dev/null +++ b/cli/options/options.go @@ -0,0 +1,486 @@ +/* +Package options contains a set of common CLI options and helper functions to use them. +*/ +package options + +import ( + "context" + "errors" + "fmt" + "net/url" + "os" + "runtime" + "slices" + "strconv" + "strings" + "time" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/services/helpers/neofs" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/urfave/cli/v2" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/term" + "gopkg.in/yaml.v3" +) + +const ( + // DefaultTimeout is the default timeout used for RPC requests. + DefaultTimeout = 10 * time.Second + // DefaultAwaitableTimeout is the default timeout used for RPC requests that + // require transaction awaiting. It is set to the approximate time of three + // Neo N3 mainnet blocks accepting. + DefaultAwaitableTimeout = 3 * 15 * time.Second +) + +const ( + // RPCEndpointFlag is a long flag name for an RPC endpoint. It can be used to + // check for flag presence in the context. + RPCEndpointFlag = "rpc-endpoint" + // NeoFSRPCEndpointFlag is a long flag name for a NeoFS RPC endpoint. + NeoFSRPCEndpointFlag = "fs-rpc-endpoint" +) + +// Wallet is a set of flags used for wallet operations. +var Wallet = []cli.Flag{ + &cli.StringFlag{ + Name: "wallet", + Aliases: []string{"w"}, + Usage: "Wallet to use to get the key for transaction signing; conflicts with --wallet-config flag", + }, + &cli.StringFlag{ + Name: "wallet-config", + Usage: "Path to wallet config to use to get the key for transaction signing; conflicts with --wallet flag", + }, +} + +// Network is a set of flags for choosing the network to operate on +// (privnet/mainnet/testnet). +var Network = []cli.Flag{ + &cli.BoolFlag{ + Name: "privnet", + Aliases: []string{"p"}, + Usage: "Use private network configuration (if --config-file option is not specified)", + }, + &cli.BoolFlag{ + Name: "mainnet", + Aliases: []string{"m"}, + Usage: "Use mainnet network configuration (if --config-file option is not specified)", + }, + &cli.BoolFlag{ + Name: "testnet", + Aliases: []string{"t"}, + Usage: "Use testnet network configuration (if --config-file option is not specified)", + }, + &cli.BoolFlag{ + Name: "unittest", + Hidden: true, + }, +} + +// RPC is a set of flags used for RPC connections (endpoint and timeout). +var RPC = []cli.Flag{ + &cli.StringFlag{ + Name: RPCEndpointFlag, + Aliases: []string{"r"}, + Usage: "RPC node address", + Required: true, + Action: cmdargs.EnsureNotEmpty("rpc-endpoint"), + }, + &cli.DurationFlag{ + Name: "timeout", + Aliases: []string{"s"}, + Value: DefaultTimeout, + Usage: "Timeout for the operation", + }, +} + +// NeoFSRPC is a set of flags used for NeoFS RPC connections (endpoint). +var NeoFSRPC = []cli.Flag{&cli.StringSliceFlag{ + Name: NeoFSRPCEndpointFlag, + Aliases: []string{"fsr"}, + Usage: "List of NeoFS storage node RPC addresses (comma-separated or multiple --fs-rpc-endpoint flags)", + Required: true, + Action: func(ctx *cli.Context, fsRpcEndpoints []string) error { + if slices.Contains(fsRpcEndpoints, "") { + return cli.Exit("NeoFS RPC endpoint cannot contain empty values", 1) + } + return nil + }, +}} + +// Historic is a flag for commands that can perform historic invocations. +var Historic = &cli.StringFlag{ + Name: "historic", + Usage: "Use historic state (height, block hash or state root hash)", +} + +// Config is a flag for commands that use node configuration. +var Config = &cli.StringFlag{ + Name: "config-path", + Usage: "Path to directory with per-network configuration files (may be overridden by --config-file option for the configuration file)", +} + +// ConfigFile is a flag for commands that use node configuration and provide +// path to the specific config file instead of config path. +var ConfigFile = &cli.StringFlag{ + Name: "config-file", + Usage: "Path to the node configuration file (overrides --config-path option)", +} + +// RelativePath is a flag for commands that use node configuration and provide +// a prefix to all relative paths in config files. +var RelativePath = &cli.StringFlag{ + Name: "relative-path", + Usage: "Prefix to all relative paths in the node configuration file", +} + +// Debug is a flag for commands that allow node in debug mode usage. +var Debug = &cli.BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + Usage: "Enable debug logging (LOTS of output, overrides configuration)", +} + +// ForceTimestampLogs is a flag for commands that run the node. This flag +// enables timestamp logging for every log record even if program is running +// not in terminal. +var ForceTimestampLogs = &cli.BoolFlag{ + Name: "force-timestamp-logs", + Usage: "Enable timestamps for log entries", +} + +var errInvalidHistoric = errors.New("invalid 'historic' parameter, neither a block number, nor a block/state hash") +var ErrNoWallet = errors.New("no wallet parameter found, specify it with the '--wallet' or '-w' flag or specify wallet config file with the '--wallet-config' flag") +var errConflictingWalletFlags = errors.New("--wallet flag conflicts with --wallet-config flag, please, provide one of them to specify wallet location") + +// GetNetwork examines Context's flags and returns the appropriate network. It +// defaults to PrivNet if no flags are given. +func GetNetwork(ctx *cli.Context) netmode.Magic { + var net = netmode.PrivNet + if ctx.Bool("testnet") { + net = netmode.TestNet + } + if ctx.Bool("mainnet") { + net = netmode.MainNet + } + if ctx.Bool("unittest") { + net = netmode.UnitTestNet + } + return net +} + +// GetTimeoutContext returns a context.Context with the default or a user-set timeout. +func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) { + dur := ctx.Duration("timeout") + if dur == 0 { + dur = DefaultTimeout + } + if !ctx.IsSet("timeout") && ctx.Bool("await") { + dur = DefaultAwaitableTimeout + } + return context.WithTimeout(context.Background(), dur) +} + +// GetRPCClient returns an RPC client instance for the given Context. +func GetRPCClient(gctx context.Context, ctx *cli.Context) (*rpcclient.Client, cli.ExitCoder) { + endpoint := ctx.String(RPCEndpointFlag) + c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{}) + if err != nil { + return nil, cli.Exit(err, 1) + } + err = c.Init() + if err != nil { + return nil, cli.Exit(err, 1) + } + return c, nil +} + +// GetNeoFSClientPool returns a NeoFS pool and a signer for the given Context. +func GetNeoFSClientPool(ctx *cli.Context, acc *wallet.Account) (user.Signer, *pool.Pool, error) { + rpcNeoFS := ctx.StringSlice(NeoFSRPCEndpointFlag) + signer := user.NewAutoIDSignerRFC6979(acc.PrivateKey().PrivateKey) + + params := pool.DefaultOptions() + params.SetHealthcheckTimeout(neofs.DefaultHealthcheckTimeout) + params.SetNodeDialTimeout(neofs.DefaultDialTimeout) + params.SetNodeStreamTimeout(neofs.DefaultStreamTimeout) + p, err := pool.New(pool.NewFlatNodeParams(rpcNeoFS), signer, params) + if err != nil { + return nil, nil, fmt.Errorf("failed to create NeoFS pool: %w", err) + } + if err = p.Dial(context.Background()); err != nil { + return nil, nil, fmt.Errorf("failed to dial NeoFS pool: %w", err) + } + return signer, p, nil +} + +// GetInvoker returns an invoker using the given RPC client, context and signers. +// It parses "--historic" parameter to adjust it. +func GetInvoker(c *rpcclient.Client, ctx *cli.Context, signers []transaction.Signer) (*invoker.Invoker, cli.ExitCoder) { + historic := ctx.String("historic") + if historic == "" { + return invoker.New(c, signers), nil + } + if index, err := strconv.ParseUint(historic, 10, 32); err == nil { + return invoker.NewHistoricAtHeight(uint32(index), c, signers), nil + } + if u256, err := util.Uint256DecodeStringLE(historic); err == nil { + // Might as well be a block hash, but it makes no practical difference. + return invoker.NewHistoricWithState(u256, c, signers), nil + } + return nil, cli.Exit(errInvalidHistoric, 1) +} + +// GetRPCWithInvoker combines GetRPCClient with GetInvoker for cases where it's +// appropriate to do so. +func GetRPCWithInvoker(gctx context.Context, ctx *cli.Context, signers []transaction.Signer) (*rpcclient.Client, *invoker.Invoker, cli.ExitCoder) { + c, err := GetRPCClient(gctx, ctx) + if err != nil { + return nil, nil, err + } + inv, err := GetInvoker(c, ctx, signers) + if err != nil { + c.Close() + return nil, nil, err + } + return c, inv, err +} + +// GetConfigFromContext looks at the path and the mode flags in the given config and +// returns an appropriate config. +func GetConfigFromContext(ctx *cli.Context) (config.Config, error) { + var ( + configFile = ctx.String("config-file") + relativePath = ctx.String("relative-path") + ) + if len(configFile) != 0 { + return config.LoadFile(configFile, relativePath) + } + var configPath = config.DefaultConfigPath + if argCp := ctx.String("config-path"); argCp != "" { + configPath = argCp + } + return config.Load(configPath, GetNetwork(ctx), relativePath) +} + +var ( + // _winfileSinkRegistered denotes whether zap has registered + // user-supplied factory for all sinks with `winfile`-prefixed scheme. + _winfileSinkRegistered bool + _winfileSinkCloser func() error +) + +// HandleLoggingParams reads logging parameters. +// If a user selected debug level -- function enables it. +// If logPath is configured -- function creates a dir and a file for logging. +// If logPath is configured on Windows -- function returns closer to be +// able to close sink for the opened log output file. +// If the program is run in TTY then logger adds timestamp to its entries. +func HandleLoggingParams(ctx *cli.Context, cfg config.ApplicationConfiguration) (*zap.Logger, *zap.AtomicLevel, func() error, error) { + var ( + level = zapcore.InfoLevel + encoding = "console" + err error + ) + if len(cfg.LogLevel) > 0 { + level, err = zapcore.ParseLevel(cfg.LogLevel) + if err != nil { + return nil, nil, nil, fmt.Errorf("log setting: %w", err) + } + } + if len(cfg.LogEncoding) > 0 { + encoding = cfg.LogEncoding + } + if ctx != nil && ctx.Bool("debug") { + level = zapcore.DebugLevel + } + + cc := zap.NewProductionConfig() + cc.DisableCaller = true + cc.DisableStacktrace = true + cc.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder + cc.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + if cfg.LogTimestamp == nil && term.IsTerminal(int(os.Stdout.Fd())) || + cfg.LogTimestamp != nil && *cfg.LogTimestamp || + ctx != nil && ctx.Bool("force-timestamp-logs") { + cc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + } else { + cc.EncoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {} + } + cc.Encoding = encoding + cc.Level = zap.NewAtomicLevelAt(level) + cc.Sampling = nil + + if logPath := cfg.LogPath; logPath != "" { + if err := io.MakeDirForFile(logPath, "logger"); err != nil { + return nil, nil, nil, err + } + + if runtime.GOOS == "windows" { + if !_winfileSinkRegistered { + // See https://github.com/uber-go/zap/issues/621. + err := zap.RegisterSink("winfile", func(u *url.URL) (zap.Sink, error) { + if u.User != nil { + return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) + } + if u.Fragment != "" { + return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u) + } + if u.RawQuery != "" { + return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u) + } + // Error messages are better if we check hostname and port separately. + if u.Port() != "" { + return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u) + } + if hn := u.Hostname(); hn != "" && hn != "localhost" { + return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) + } + switch u.Path { + case "stdout": + return os.Stdout, nil + case "stderr": + return os.Stderr, nil + } + f, err := os.OpenFile(u.Path[1:], // Remove leading slash left after url.Parse. + os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + _winfileSinkCloser = func() error { + _winfileSinkCloser = nil + return f.Close() + } + return f, err + }) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to register windows-specific sinc: %w", err) + } + _winfileSinkRegistered = true + } + logPath = "winfile:///" + logPath + } + + cc.OutputPaths = []string{logPath} + } + + log, err := cc.Build() + return log, &cc.Level, _winfileSinkCloser, err +} + +// GetRPCWithActor returns an RPC client instance and Actor instance for the given context. +func GetRPCWithActor(gctx context.Context, ctx *cli.Context, signers []actor.SignerAccount) (*rpcclient.Client, *actor.Actor, cli.ExitCoder) { + c, err := GetRPCClient(gctx, ctx) + if err != nil { + return nil, nil, err + } + + a, actorErr := actor.New(c, signers) + if actorErr != nil { + c.Close() + return nil, nil, cli.Exit(fmt.Errorf("failed to create Actor: %w", actorErr), 1) + } + return c, a, nil +} + +// GetAccFromContext returns account and wallet from context. If address is not set, default address is used. +func GetAccFromContext(ctx *cli.Context) (*wallet.Account, *wallet.Wallet, error) { + var addr util.Uint160 + + wPath := ctx.String("wallet") + walletConfigPath := ctx.String("wallet-config") + if len(wPath) != 0 && len(walletConfigPath) != 0 { + return nil, nil, errConflictingWalletFlags + } + if len(wPath) == 0 && len(walletConfigPath) == 0 { + return nil, nil, ErrNoWallet + } + var pass *string + if len(walletConfigPath) != 0 { + cfg, err := ReadWalletConfig(walletConfigPath) + if err != nil { + return nil, nil, err + } + wPath = cfg.Path + pass = &cfg.Password + } + + wall, err := wallet.NewWalletFromFile(wPath) + if err != nil { + return nil, nil, err + } + addrFlag := ctx.Generic("address").(*flags.Address) + if addrFlag.IsSet { + addr = addrFlag.Uint160() + } else { + addr = wall.GetChangeAddress() + if addr.Equals(util.Uint160{}) { + return nil, wall, errors.New("can't get default address") + } + } + + acc, err := GetUnlockedAccount(wall, addr, pass) + return acc, wall, err +} + +// GetUnlockedAccount returns account from wallet, address and uses pass to unlock specified account if given. +// If the password is not given, then it is requested from user. +func GetUnlockedAccount(wall *wallet.Wallet, addr util.Uint160, pass *string) (*wallet.Account, error) { + acc := wall.GetAccount(addr) + if acc == nil { + return nil, fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)) + } + + if acc.CanSign() || acc.EncryptedWIF == "" { + return acc, nil + } + + if pass == nil { + rawPass, err := input.ReadPassword( + fmt.Sprintf("Enter account %s password > ", address.Uint160ToString(addr))) + if err != nil { + return nil, fmt.Errorf("error reading password: %w", err) + } + trimmed := strings.TrimRight(string(rawPass), "\n") + pass = &trimmed + } + err := acc.Decrypt(*pass, wall.Scrypt) + if err != nil { + return nil, err + } + return acc, nil +} + +// ReadWalletConfig reads wallet config from the given path. +func ReadWalletConfig(configPath string) (*config.Wallet, error) { + file, err := os.Open(configPath) + if err != nil { + return nil, err + } + defer file.Close() + + configData, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("unable to read wallet config: %w", err) + } + + cfg := &config.Wallet{} + + err = yaml.Unmarshal(configData, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal wallet config YAML: %w", err) + } + return cfg, nil +} diff --git a/cli/options/options_test.go b/cli/options/options_test.go new file mode 100644 index 0000000..649d9b2 --- /dev/null +++ b/cli/options/options_test.go @@ -0,0 +1,57 @@ +package options + +import ( + "flag" + "testing" + "time" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func TestGetNetwork(t *testing.T) { + t.Run("privnet", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + ctx := cli.NewContext(cli.NewApp(), set, nil) + require.Equal(t, netmode.PrivNet, GetNetwork(ctx)) + }) + + t.Run("testnet", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.Bool("testnet", true, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + require.Equal(t, netmode.TestNet, GetNetwork(ctx)) + }) + + t.Run("mainnet", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.Bool("mainnet", true, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + require.Equal(t, netmode.MainNet, GetNetwork(ctx)) + }) +} + +func TestGetTimeoutContext(t *testing.T) { + t.Run("default", func(t *testing.T) { + start := time.Now() + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + ctx := cli.NewContext(cli.NewApp(), set, nil) + actualCtx, _ := GetTimeoutContext(ctx) + end := time.Now().Add(DefaultTimeout) + dl, _ := actualCtx.Deadline() + require.True(t, start.Before(dl) && (dl.Before(end) || dl.Equal(end))) + }) + + t.Run("set", func(t *testing.T) { + start := time.Now() + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.Duration("timeout", time.Duration(20), "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + actualCtx, _ := GetTimeoutContext(ctx) + end := time.Now().Add(time.Nanosecond * 20) + dl, _ := actualCtx.Deadline() + require.True(t, start.Before(dl) && (dl.Before(end) || dl.Equal(end))) + }) +} diff --git a/cli/paramcontext/context.go b/cli/paramcontext/context.go new file mode 100644 index 0000000..3d6429c --- /dev/null +++ b/cli/paramcontext/context.go @@ -0,0 +1,50 @@ +package paramcontext + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" + "github.com/nspcc-dev/neo-go/pkg/wallet" +) + +// InitAndSave creates an incompletely signed transaction which can be used +// as an input to `multisig sign`. If a wallet.Account is given and can sign, +// it's signed as well using it. +func InitAndSave(net netmode.Magic, tx *transaction.Transaction, acc *wallet.Account, filename string) error { + scCtx := context.NewParameterContext(context.TransactionType, net, tx) + if acc != nil && acc.CanSign() { + sign := acc.SignHashable(net, tx) + if err := scCtx.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil { + return fmt.Errorf("can't add signature: %w", err) + } + } + return Save(scCtx, filename) +} + +// Read reads the parameter context from the file. +func Read(filename string) (*context.ParameterContext, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("can't read input file: %w", err) + } + + c := new(context.ParameterContext) + if err := json.Unmarshal(data, c); err != nil { + return nil, fmt.Errorf("can't parse transaction: %w", err) + } + return c, nil +} + +// Save writes the parameter context to the file. +func Save(c *context.ParameterContext, filename string) error { + if data, err := json.Marshal(c); err != nil { + return fmt.Errorf("can't marshal transaction: %w", err) + } else if err := os.WriteFile(filename, data, 0644); err != nil { + return fmt.Errorf("can't write transaction to file: %w", err) + } + return nil +} diff --git a/cli/query/query.go b/cli/query/query.go new file mode 100644 index 0000000..5150bf3 --- /dev/null +++ b/cli/query/query.go @@ -0,0 +1,379 @@ +package query + +import ( + "bytes" + "cmp" + "encoding/base64" + "fmt" + "slices" + "strconv" + "strings" + "text/tabwriter" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" + "github.com/urfave/cli/v2" +) + +// NewCommands returns 'query' command. +func NewCommands() []*cli.Command { + queryTxFlags := append([]cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Output full tx info and execution logs", + }, + }, options.RPC...) + queryNotaryPoolFlags := append([]cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Output full main tx info", + }, + }, options.RPC...) + return []*cli.Command{{ + Name: "query", + Usage: "Query data from RPC node", + Subcommands: []*cli.Command{ + { + Name: "candidates", + Usage: "Get candidates and votes", + UsageText: "neo-go query candidates -r endpoint [-s timeout]", + Action: queryCandidates, + Flags: options.RPC, + }, + { + Name: "committee", + Usage: "Get committee list", + UsageText: "neo-go query committee -r endpoint [-s timeout]", + Action: queryCommittee, + Flags: options.RPC, + }, + { + Name: "height", + Usage: "Get node height", + UsageText: "neo-go query height -r endpoint [-s timeout]", + Action: queryHeight, + Flags: options.RPC, + }, + { + Name: "notarypool", + Usage: "Query the content of the notary pool", + UsageText: "neo-go query notarypool -r endpoint [-s timeout] [-v]", + Action: queryNotaryPool, + Flags: queryNotaryPoolFlags, + }, + { + Name: "tx", + Usage: "Query transaction status", + UsageText: "neo-go query tx -r endpoint [-s timeout] [-v] ", + Action: queryTx, + Flags: queryTxFlags, + }, + { + Name: "voter", + Usage: "Print NEO holder account state", + UsageText: "neo-go query voter -r endpoint [-s timeout]
", + Action: queryVoter, + Flags: options.RPC, + }, + }, + }} +} + +func queryTx(ctx *cli.Context) error { + args := ctx.Args().Slice() + if len(args) == 0 { + return cli.Exit("transaction hash is missing", 1) + } else if len(args) > 1 { + return cli.Exit("only one transaction hash is accepted", 1) + } + + txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x")) + if err != nil { + return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1) + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return cli.Exit(err, 1) + } + + txOut, err := c.GetRawTransactionVerbose(txHash) + if err != nil { + return cli.Exit(err, 1) + } + + var res *result.ApplicationLog + if !txOut.Blockhash.Equals(util.Uint256{}) { + res, err = c.GetApplicationLog(txHash, nil) + if err != nil { + return cli.Exit(err, 1) + } + } + + err = DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose")) + if err != nil { + return cli.Exit(err, 1) + } + return nil +} + +func DumpApplicationLog( + ctx *cli.Context, + res *result.ApplicationLog, + tx *transaction.Transaction, + txMeta *result.TransactionMetadata, + verbose bool, + fallbacks ...int) error { + var buf []byte + + buf = fmt.Appendf(buf, "Hash:\t%s\n", tx.Hash().StringLE()) + buf = fmt.Appendf(buf, "OnChain:\t%t\n", res != nil) + if res == nil { + buf = fmt.Appendf(buf, "ValidUntil:\t%s\n", strconv.FormatUint(uint64(tx.ValidUntilBlock), 10)) + if len(fallbacks) != 0 { + buf = fmt.Appendf(buf, "Fallbacks:\t%d\n", fallbacks[0]) + } + } else { + if txMeta != nil { + buf = fmt.Appendf(buf, "BlockHash:\t%s\n", txMeta.Blockhash.StringLE()) + } + if len(res.Executions) != 1 { + buf = fmt.Appendf(buf, "Success:\tunknown (no execution data)\n") + } else { + buf = fmt.Appendf(buf, "Success:\t%t\n", res.Executions[0].VMState == vmstate.Halt) + } + } + if verbose { + for _, sig := range tx.Signers { + buf = fmt.Appendf(buf, "Signer:\t%s (%s)\n", address.Uint160ToString(sig.Account), sig.Scopes) + } + buf = fmt.Appendf(buf, "SystemFee:\t%s GAS\n", fixedn.Fixed8(tx.SystemFee).String()) + buf = fmt.Appendf(buf, "NetworkFee:\t%s GAS\n", fixedn.Fixed8(tx.NetworkFee).String()) + buf = fmt.Appendf(buf, "Script:\t%s\n", base64.StdEncoding.EncodeToString(tx.Script)) + if len(fallbacks) == 0 { + v := vm.New() + v.Load(tx.Script) + opts := bytes.NewBuffer(nil) + v.PrintOps(opts) + buf = append(buf, opts.Bytes()...) + if res != nil { + for _, e := range res.Executions { + if e.VMState != vmstate.Halt { + buf = fmt.Appendf(buf, "Exception:\t%s\n", e.FaultException) + } + } + } + } + } + tw := tabwriter.NewWriter(ctx.App.Writer, 0, 4, 4, '\t', 0) + _, err := tw.Write(buf) + if err != nil { + return err + } + return tw.Flush() +} + +func queryCandidates(ctx *cli.Context) error { + var err error + + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return cli.Exit(err, 1) + } + + vals, err := c.GetCandidates() + if err != nil { + return cli.Exit(err, 1) + } + comm, err := c.GetCommittee() + if err != nil { + return cli.Exit(err, 1) + } + + slices.SortFunc(vals, func(a, b result.Candidate) int { + if a.Active && !b.Active { + return 1 + } + if !a.Active && b.Active { + return -1 + } + return cmp.Or( + cmp.Compare(a.Votes, b.Votes), + a.PublicKey.Cmp(&b.PublicKey), + ) + }) + var res []byte + res = fmt.Appendf(res, "Key\tVotes\tCommittee\tConsensus\n") + for _, val := range vals { + res = fmt.Appendf(res, "%s\t%d\t%t\t%t\n", val.PublicKey.StringCompressed(), val.Votes, comm.Contains(&val.PublicKey), val.Active) + } + tw := tabwriter.NewWriter(ctx.App.Writer, 0, 2, 2, ' ', 0) + _, err = tw.Write(res) + if err != nil { + return err + } + return tw.Flush() +} + +func queryCommittee(ctx *cli.Context) error { + var err error + + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return cli.Exit(err, 1) + } + + comm, err := c.GetCommittee() + if err != nil { + return cli.Exit(err, 1) + } + + for _, k := range comm { + fmt.Fprintln(ctx.App.Writer, k.StringCompressed()) + } + return nil +} + +func queryHeight(ctx *cli.Context) error { + var err error + + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return cli.Exit(err, 1) + } + + blockCount, err := c.GetBlockCount() + if err != nil { + return cli.Exit(err, 1) + } + blockHeight := blockCount - 1 // GetBlockCount returns block count (including 0), not the highest block index. + + fmt.Fprintf(ctx.App.Writer, "Latest block: %d\n", blockHeight) + + stateHeight, err := c.GetStateHeight() + if err == nil { // We can be talking to a node without getstateheight request support. + fmt.Fprintf(ctx.App.Writer, "Validated state: %d\n", stateHeight.Validated) + } + + return nil +} + +func queryVoter(ctx *cli.Context) error { + args := ctx.Args().Slice() + if len(args) == 0 { + return cli.Exit("no address specified", 1) + } else if len(args) > 1 { + return cli.Exit("this command only accepts one address", 1) + } + + addr, err := flags.ParseAddress(args[0]) + if err != nil { + return cli.Exit(fmt.Sprintf("wrong address: %s", args[0]), 1) + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + c, exitErr := options.GetRPCClient(gctx, ctx) + if exitErr != nil { + return exitErr + } + + neoToken := neo.NewReader(invoker.New(c, nil)) + + st, err := neoToken.GetAccountState(addr) + if err != nil { + return cli.Exit(err, 1) + } + if st == nil { + st = new(state.NEOBalance) + } + dec, err := neoToken.Decimals() + if err != nil { + return cli.Exit(fmt.Errorf("failed to get decimals: %w", err), 1) + } + voted := "null" + if st.VoteTo != nil { + voted = fmt.Sprintf("%s (%s)", st.VoteTo.StringCompressed(), address.Uint160ToString(st.VoteTo.GetScriptHash())) + } + fmt.Fprintf(ctx.App.Writer, "\tVoted: %s\n", voted) + fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", fixedn.ToString(&st.Balance, int(dec))) + fmt.Fprintf(ctx.App.Writer, "\tBlock: %d\n", st.BalanceHeight) + return nil +} + +func queryNotaryPool(ctx *cli.Context) error { + var err error + + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return cli.Exit(err, 1) + } + + h, err := c.GetBlockCount() + if err != nil { + return cli.Exit(err, 1) + } + fmt.Fprintf(ctx.App.Writer, "Current height: %d\n", h) + + p, err := c.GetRawNotaryPool() + if err != nil { + return cli.Exit(err, 1) + } + for m, fbs := range p.Hashes { + tx, err := c.GetRawNotaryTransaction(m) + if err != nil { + fmt.Fprintf(ctx.App.Writer, "%s (%d fallbacks): %s\n", m.StringLE(), len(fbs), err) + continue + } + err = DumpApplicationLog(ctx, nil, tx, nil, ctx.Bool("verbose"), len(fbs)) + if err != nil { + return cli.Exit(err, 1) + } + } + + return nil +} diff --git a/cli/query/query_test.go b/cli/query/query_test.go new file mode 100644 index 0000000..e508412 --- /dev/null +++ b/cli/query/query_test.go @@ -0,0 +1,267 @@ +package query_test + +import ( + "context" + "encoding/base64" + "fmt" + "math/big" + "regexp" + "strconv" + "strings" + "testing" + "time" + + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/require" +) + +func TestQueryTx(t *testing.T) { + e := testcli.NewExecutorSuspended(t) + + w, err := wallet.NewWalletFromFile("../testdata/testwallet.json") + require.NoError(t, err) + + transferArgs := []string{ + "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--to", w.Accounts[0].Address, + "--token", "NEO", + "--from", testcli.ValidatorAddr, + "--force", + } + + e.In.WriteString("one\r") + e.Run(t, append(transferArgs, "--amount", "1")...) + line := e.GetNextLine(t) + txHash, err := util.Uint256DecodeStringLE(line) + require.NoError(t, err) + + tx, ok := e.Chain.GetMemPool().TryGetValue(txHash) + require.True(t, ok) + + args := []string{"neo-go", "query", "tx", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} + e.Run(t, append(args, txHash.StringLE())...) + e.CheckNextLine(t, `Hash:\s+`+txHash.StringLE()) + e.CheckNextLine(t, `OnChain:\s+false`) + e.CheckNextLine(t, `ValidUntil:\s+`+strconv.FormatUint(uint64(tx.ValidUntilBlock), 10)) + e.CheckEOF(t) + + go e.Chain.Run() + require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(txHash, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50) + + e.Run(t, append(args, txHash.StringLE())...) + e.CheckNextLine(t, `Hash:\s+`+txHash.StringLE()) + e.CheckNextLine(t, `OnChain:\s+true`) + + _, height, err := e.Chain.GetTransaction(txHash) + require.NoError(t, err) + e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(height).StringLE()) + e.CheckNextLine(t, `Success:\s+true`) + e.CheckEOF(t) + + t.Run("verbose", func(t *testing.T) { + e.Run(t, append(args, "--verbose", txHash.StringLE())...) + compareQueryTxVerbose(t, e, tx) + + t.Run("FAULT", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, "neo-go", "contract", "invokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", testcli.ValidatorAddr, + "--force", + random.Uint160().StringLE(), + "randomMethod") + + e.CheckNextLine(t, `Warning:`) + e.CheckNextLine(t, "Sending transaction") + line := strings.TrimPrefix(e.GetNextLine(t), "Sent invocation transaction ") + txHash, err := util.Uint256DecodeStringLE(line) + require.NoError(t, err) + + require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(txHash, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50) + + tx, _, err := e.Chain.GetTransaction(txHash) + require.NoError(t, err) + e.Run(t, append(args, "--verbose", txHash.StringLE())...) + compareQueryTxVerbose(t, e, tx) + }) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("missing tx argument", func(t *testing.T) { + e.RunWithError(t, args...) + }) + t.Run("excessive arguments", func(t *testing.T) { + e.RunWithError(t, append(args, txHash.StringLE(), txHash.StringLE())...) + }) + t.Run("invalid hash", func(t *testing.T) { + e.RunWithError(t, append(args, "notahash")...) + }) + t.Run("good hash, missing tx", func(t *testing.T) { + e.RunWithError(t, append(args, random.Uint256().StringLE())...) + }) + }) +} + +func compareQueryTxVerbose(t *testing.T, e *testcli.Executor, tx *transaction.Transaction, fallbacks ...int) { + if len(fallbacks) > 0 { + e.CheckNextLine(t, `Current height:\s+(\d|\.)+`) + } + e.CheckNextLine(t, `Hash:\s+`+tx.Hash().StringLE()) + e.CheckNextLine(t, `OnChain:\s+`+strconv.FormatBool(len(fallbacks) == 0)) + var ( + res []state.AppExecResult + err error + ) + if len(fallbacks) == 0 { + _, height, err := e.Chain.GetTransaction(tx.Hash()) + require.NoError(t, err) + e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(height).StringLE()) + res, _ = e.Chain.GetAppExecResults(tx.Hash(), trigger.Application) + e.CheckNextLine(t, fmt.Sprintf(`Success:\s+%t`, res[0].VMState == vmstate.Halt)) + } else { + e.CheckNextLine(t, `ValidUntil:\s+`+strconv.FormatUint(uint64(tx.ValidUntilBlock), 10)) + e.CheckNextLine(t, `Fallbacks:\s+`+strconv.Itoa(len(fallbacks))) + } + + for _, s := range tx.Signers { + e.CheckNextLine(t, fmt.Sprintf(`Signer:\s+%s\s*\(%s\)`, address.Uint160ToString(s.Account), s.Scopes.String())) + } + e.CheckNextLine(t, `SystemFee:\s+`+fixedn.Fixed8(tx.SystemFee).String()+" GAS$") + e.CheckNextLine(t, `NetworkFee:\s+`+fixedn.Fixed8(tx.NetworkFee).String()+" GAS$") + e.CheckNextLine(t, `Script:\s+`+regexp.QuoteMeta(base64.StdEncoding.EncodeToString(tx.Script))) + if len(fallbacks) == 0 { + c := vm.NewContext(tx.Script) + n := 0 + for ; c.NextIP() < c.LenInstr(); _, _, err = c.Next() { + require.NoError(t, err) + n++ + } + e.CheckScriptDump(t, n) + + if res[0].VMState != vmstate.Halt { + e.CheckNextLine(t, `Exception:\s+`+regexp.QuoteMeta(res[0].FaultException)) + } + } + + e.CheckEOF(t) +} + +func TestQueryHeight(t *testing.T) { + e := testcli.NewExecutor(t, true) + + args := []string{"neo-go", "query", "height", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} + e.Run(t, args...) + e.CheckNextLine(t, `^Latest block: [0-9]+$`) + e.CheckNextLine(t, `^Validated state: [0-9]+$`) + e.CheckEOF(t) + t.Run("excessive arguments", func(t *testing.T) { + e.RunWithError(t, append(args, "something")...) + }) +} + +func TestQueryNotaryPool(t *testing.T) { + e := testcli.NewExecutorWithConfig(t, true, true, func(cfg *config.Config) { + cfg.ProtocolConfiguration.Hardforks = map[string]uint32{ + config.HFFaun.String(): 0, + } + }) + endpoint := "http://" + e.RPC.Addresses()[0] + + w, err := wallet.NewWalletFromFile("../testdata/testwallet.json") + require.NoError(t, err) + acc := w.Accounts[0] + require.NoError(t, acc.Decrypt(testcli.TestWalletPass, w.Scrypt)) + + // Transfer some GAS to the account. + transferArgs := []string{ + "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--to", acc.Address, + "--token", "GAS", + "--amount", "10000", + "--from", testcli.ValidatorAddr, + "--force", + } + e.In.WriteString("one\r") + e.Run(t, transferArgs...) + line := e.GetNextLine(t) + txHash, err := util.Uint256DecodeStringLE(line) + require.NoError(t, err) + require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(txHash, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50) + + // Make a notary deposit. + c, err := rpcclient.New(context.Background(), endpoint, rpcclient.Options{}) + require.NoError(t, err) + require.NoError(t, c.Init()) + signer := actor.SignerAccount{ + Signer: transaction.Signer{Account: acc.ScriptHash(), Scopes: transaction.Global}, + Account: acc, + } + act, err := actor.New(c, []actor.SignerAccount{signer}) + require.NoError(t, err) + g := gas.New(act) + h, _, err := g.Transfer(acc.ScriptHash(), nativehashes.Notary, big.NewInt(20000000), []any{stackitem.Null{}, e.Chain.BlockHeight() + 1000}) + require.NoError(t, err) + require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(h, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50) + + // Submit some notary request. + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + fakeAcc := notary.FakeSimpleAccount(pk.PublicKey()) + ntr, err := notary.NewActor(c, []actor.SignerAccount{ + signer, + { + Signer: transaction.Signer{Account: fakeAcc.ScriptHash(), Scopes: transaction.Global}, + Account: fakeAcc, + }, + }, acc) + require.NoError(t, err) + tx, err := ntr.MakeCall(nativehashes.GasToken, "transfer", acc.ScriptHash(), acc.ScriptHash(), big.NewInt(1), nil) + mainH, _, vub, err := ntr.Notarize(tx, err) + require.NoError(t, err) + + // Check the notary pool. + args := []string{"neo-go", "query", "notarypool", "--rpc-endpoint", endpoint} + t.Run("silent", func(t *testing.T) { + e.Run(t, args...) + e.CheckNextLine(t, `Current height:\s+(\d|\.)+`) + e.CheckNextLine(t, `Hash:\s+`+mainH.StringLE()) + e.CheckNextLine(t, `OnChain:\s+false`) + e.CheckNextLine(t, `ValidUntil:\s+`+strconv.FormatUint(uint64(vub), 10)) + e.CheckNextLine(t, `Fallbacks:\s+1`) + e.CheckEOF(t) + }) + + t.Run("verbose", func(t *testing.T) { + e.Run(t, append(args, "--verbose")...) + compareQueryTxVerbose(t, e, tx, 1) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("excessive arguments", func(t *testing.T) { + e.RunWithError(t, append(args, "bla")...) + }) + }) +} diff --git a/cli/server/cli_dump_test.go b/cli/server/cli_dump_test.go new file mode 100644 index 0000000..acc5eb5 --- /dev/null +++ b/cli/server/cli_dump_test.go @@ -0,0 +1,163 @@ +package server_test + +import ( + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// generated via `go run ./scripts/gendump/main.go --out ./cli/server/testdata/chain50x2.acc --blocks 50 --txs 2`. +const inDump = "./testdata/chain50x2.acc" + +func TestDBRestoreDump(t *testing.T) { + tmpDir := t.TempDir() + + loadConfig := func(t *testing.T) config.Config { + chainPath := filepath.Join(tmpDir, "neogotestchain") + cfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + return cfg + } + + cfg := loadConfig(t) + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + + e := testcli.NewExecutor(t, false) + + stateDump := filepath.Join(tmpDir, "neogo.teststate") + baseArgs := []string{"neo-go", "db", "restore", "--unittest", + "--config-path", tmpDir, "--in", inDump, "--dump", stateDump} + + t.Run("excessive restore parameters", func(t *testing.T) { + e.RunWithError(t, append(baseArgs, "something")...) + }) + // First 15 blocks. + e.Run(t, append(baseArgs, "--count", "15")...) + + // Big count. + e.RunWithError(t, append(baseArgs, "--count", "1000")...) + + // Continue 15..25 + e.Run(t, append(baseArgs, "--count", "10")...) + + // Continue till end. + e.Run(t, baseArgs...) + + // Dump and compare. + dumpPath := filepath.Join(tmpDir, "testdump.acc") + incDumpPath := filepath.Join(tmpDir, "testincdump.acc") + + t.Run("missing config", func(t *testing.T) { + e.RunWithError(t, "neo-go", "db", "dump", "--privnet", + "--config-path", tmpDir, "--out", dumpPath) + }) + t.Run("bad logger config", func(t *testing.T) { + badConfigDir := t.TempDir() + logfile := filepath.Join(badConfigDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg = loadConfig(t) + cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") + out, err = yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath = filepath.Join(badConfigDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + + e.RunWithError(t, "neo-go", "db", "dump", "--unittest", + "--config-path", badConfigDir, "--out", dumpPath) + }) + t.Run("bad storage config", func(t *testing.T) { + badConfigDir := t.TempDir() + logfile := filepath.Join(badConfigDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg = loadConfig(t) + cfg.ApplicationConfiguration.DBConfiguration.Type = "" + out, err = yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath = filepath.Join(badConfigDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + + e.RunWithError(t, "neo-go", "db", "dump", "--unittest", + "--config-path", badConfigDir, "--out", dumpPath) + }) + + baseCmd := []string{"neo-go", "db", "dump", "--unittest", + "--config-path", tmpDir, "--out", dumpPath} + + t.Run("invalid start/count", func(t *testing.T) { + e.RunWithError(t, append(baseCmd, "--start", "5", "--count", strconv.Itoa(50-5+1+1))...) + }) + t.Run("excessive dump parameters", func(t *testing.T) { + e.RunWithError(t, append(baseCmd, "something")...) + }) + + e.Run(t, append(baseCmd, "--non-incremental", "--out", dumpPath)...) + e.Run(t, append(baseCmd, "--out", incDumpPath)...) + + d1, err := os.ReadFile(inDump) + require.NoError(t, err) + d2, err := os.ReadFile(dumpPath) + require.NoError(t, err) + require.Equal(t, d1, d2, "dumps differ") + d3, err := os.ReadFile(incDumpPath) + require.NoError(t, err) + require.Equal(t, append([]byte{0, 0, 0, 0}, d1...), d3, "dumps differ") +} + +func TestDBDumpRestoreIncremental(t *testing.T) { + tmpDir := t.TempDir() + chainPath := filepath.Join(tmpDir, "neogotestchain") + nonincDump := filepath.Join(tmpDir, "nonincDump.acc") + incDump := filepath.Join(tmpDir, "incDump.acc") + + cfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + + e := testcli.NewExecutor(t, false) + + // Create DB from dump. + e.Run(t, "neo-go", "db", "restore", "--unittest", "--config-path", tmpDir, "--in", inDump) + + // Create two dumps: non-incremental and incremental. + dumpBaseArgs := []string{"neo-go", "db", "dump", "--unittest", + "--config-path", tmpDir} + + // Dump first 15 blocks to a non-incremental dump. + e.Run(t, append(dumpBaseArgs, "--out", nonincDump, "--count", "15")...) + + // Dump second 15 blocks to an incremental dump. + e.Run(t, append(dumpBaseArgs, "--out", incDump, "--start", "15", "--count", "15")...) + + // Clean the DB. + require.NoError(t, os.RemoveAll(chainPath)) + + // Restore chain from two dumps. + restoreBaseArgs := []string{"neo-go", "db", "restore", "--unittest", "--config-path", tmpDir} + + // Restore first 15 blocks from non-incremental dump. + e.Run(t, append(restoreBaseArgs, "--in", nonincDump)...) + + // Restore second 15 blocks from incremental dump. + e.Run(t, append(restoreBaseArgs, "--in", incDump, "--count", "15")...) +} diff --git a/cli/server/cli_server_test.go b/cli/server/cli_server_test.go new file mode 100644 index 0000000..5e76d45 --- /dev/null +++ b/cli/server/cli_server_test.go @@ -0,0 +1,132 @@ +package server_test + +import ( + "errors" + "io" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/nspcc-dev/neo-go/cli/server" + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestServerStart(t *testing.T) { + tmpDir := t.TempDir() + goodCfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + ptr := &goodCfg + saveCfg := func(t *testing.T, f func(cfg *config.Config)) string { + cfg := *ptr + chainPath := filepath.Join(t.TempDir(), "neogotestchain") + cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + f(&cfg) + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + t.Cleanup(func() { + require.NoError(t, os.Remove(cfgPath)) + }) + return cfgPath + } + + baseCmd := []string{"neo-go", "node", "--unittest", "--config-path", tmpDir} + e := testcli.NewExecutor(t, false) + + t.Run("invalid config path", func(t *testing.T) { + e.RunWithError(t, baseCmd...) + }) + t.Run("bad logger config", func(t *testing.T) { + badConfigDir := t.TempDir() + logfile := filepath.Join(badConfigDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid storage", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.DBConfiguration.Type = "" + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("stateroot service is on && StateRootInHeader=true", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.StateRoot.Enabled = true + cfg.ProtocolConfiguration.StateRootInHeader = true + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid Oracle config", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.Oracle.Enabled = true + cfg.ApplicationConfiguration.Oracle.UnlockWallet.Path = "bad_orc_wallet.json" + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid consensus config", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.Consensus.Enabled = true + cfg.ApplicationConfiguration.Consensus.UnlockWallet.Path = "bad_consensus_wallet.json" + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid Notary config", func(t *testing.T) { + t.Run("malformed config", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ProtocolConfiguration.P2PSigExtensions = false + cfg.ApplicationConfiguration.P2PNotary.Enabled = true + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid wallet", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ProtocolConfiguration.P2PSigExtensions = true + cfg.ApplicationConfiguration.P2PNotary.Enabled = true + cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path = "bad_notary_wallet.json" + }) + e.RunWithError(t, baseCmd...) + }) + }) + // We can't properly shutdown server on windows and release the resources. + // Also, windows doesn't support SIGHUP and SIGINT. + if runtime.GOOS != "windows" { + saveCfg(t, func(cfg *config.Config) {}) + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, append(baseCmd, "something")...) + }) + t.Run("good", func(t *testing.T) { + go func() { + e.Run(t, baseCmd...) + }() + + var line string + require.Eventually(t, func() bool { + line, err = e.Out.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + t.Fatalf("unexpected error while reading CLI output: %s", err) + } + return err == nil + }, 2*time.Second, 100*time.Millisecond) + for expected := range strings.SplitSeq(server.Logo(), "\n") { + // It should be regexp, so escape all backslashes. + expected = strings.ReplaceAll(expected, `\`, `\\`) + e.CheckLine(t, line, expected) + line = e.GetNextLine(t) + } + e.CheckNextLine(t, "") + e.CheckEOF(t) + }) + } +} diff --git a/cli/server/dump.go b/cli/server/dump.go new file mode 100644 index 0000000..d7b6a18 --- /dev/null +++ b/cli/server/dump.go @@ -0,0 +1,102 @@ +package server + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dboper" +) + +type dump []blockDump + +type blockDump struct { + Block uint32 `json:"block"` + Size int `json:"size"` + Storage []dboper.Operation `json:"storage"` +} + +func newDump() *dump { + return new(dump) +} + +func (d *dump) add(index uint32, batch *storage.MemBatch) { + ops := storage.BatchToOperations(batch) + *d = append(*d, blockDump{ + Block: index, + Size: len(ops), + Storage: ops, + }) +} + +func (d *dump) tryPersist(prefix string, index uint32) error { + if len(*d) == 0 { + return nil + } + path, err := getPath(prefix, index) + if err != nil { + return err + } + old, err := readFile(path) + if err == nil { + *old = append(*old, *d...) + } else { + old = d + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + if err := enc.Encode(*old); err != nil { + return err + } + + *d = (*d)[:0] + + return nil +} + +func readFile(path string) (*dump, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + d := newDump() + if err := json.Unmarshal(data, d); err != nil { + return nil, err + } + return d, err +} + +// getPath returns filename for storing blocks up to index. +// Directory structure is the following: +// https://github.com/NeoResearch/neo-storage-audit#folder-organization-where-to-find-the-desired-block +// Dir `BlockStorage_$DIRNO` contains blocks up to $DIRNO (from $DIRNO-100k) +// Inside it there are files grouped by 1k blocks. +// File dump-block-$FILENO.json contains blocks from $FILENO-999, $FILENO +// Example: file `BlockStorage_100000/dump-block-6000.json` contains blocks from 5001 to 6000. +func getPath(prefix string, index uint32) (string, error) { + dirN := ((index + 99999) / 100000) * 100000 + dir := fmt.Sprintf("BlockStorage_%d", dirN) + + path := filepath.Join(prefix, dir) + info, err := os.Stat(path) + if os.IsNotExist(err) { + err := os.MkdirAll(path, os.ModePerm) + if err != nil { + return "", err + } + } else if !info.IsDir() { + return "", fmt.Errorf("file `%s` is not a directory", path) + } + + fileN := ((index + 999) / 1000) * 1000 + file := fmt.Sprintf("dump-block-%d.json", fileN) + return filepath.Join(path, file), nil +} diff --git a/cli/server/dump_bin.go b/cli/server/dump_bin.go new file mode 100644 index 0000000..66a5d3e --- /dev/null +++ b/cli/server/dump_bin.go @@ -0,0 +1,87 @@ +package server + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/urfave/cli/v2" +) + +func dumpBin(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return cli.Exit(err, 1) + } + log, _, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration) + if err != nil { + return cli.Exit(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + count := uint32(ctx.Uint("count")) + start := uint32(ctx.Uint("start")) + + chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log) + if err != nil { + return err + } + defer func() { + pprof.ShutDown() + prometheus.ShutDown() + chain.Close() + }() + + blocksCount := chain.BlockHeight() + 1 + if start+count > blocksCount { + return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", blocksCount-1, count, start), 1) + } + if count == 0 { + count = blocksCount - start + } + + out := ctx.String("out") + if out == "" { + return cli.Exit("output directory is not specified", 1) + } + if _, err = os.Stat(out); os.IsNotExist(err) { + if err = os.MkdirAll(out, os.ModePerm); err != nil { + return cli.Exit(fmt.Sprintf("failed to create directory %s: %s", out, err), 1) + } + } + if err != nil { + return cli.Exit(fmt.Sprintf("failed to check directory %s: %s", out, err), 1) + } + + for i := start; i < start+count; i++ { + blk, err := chain.GetBlock(chain.GetHeaderHash(i)) + if err != nil { + return cli.Exit(fmt.Sprintf("failed to get block %d: %s", i, err), 1) + } + filePath := filepath.Join(out, fmt.Sprintf("block-%d.bin", i)) + if err = saveBlockToFile(blk, filePath); err != nil { + return cli.Exit(fmt.Sprintf("failed to save block %d to file %s: %s", i, filePath, err), 1) + } + } + return nil +} + +func saveBlockToFile(blk *block.Block, filePath string) error { + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + writer := io.NewBinWriterFromIO(file) + blk.EncodeBinary(writer) + return writer.Err +} diff --git a/cli/server/dump_bin_test.go b/cli/server/dump_bin_test.go new file mode 100644 index 0000000..0524f2c --- /dev/null +++ b/cli/server/dump_bin_test.go @@ -0,0 +1,91 @@ +package server_test + +import ( + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestDumpBin(t *testing.T) { + tmpDir := t.TempDir() + + loadConfig := func(t *testing.T) config.Config { + chainPath := filepath.Join(tmpDir, "neogotestchain") + cfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + return cfg + } + + cfg := loadConfig(t) + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + + e := testcli.NewExecutor(t, false) + + restoreArgs := []string{"neo-go", "db", "restore", + "--config-file", cfgPath, "--in", inDump} + e.Run(t, restoreArgs...) + + t.Run("missing output directory", func(t *testing.T) { + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", ""} + e.RunWithErrorCheck(t, "output directory is not specified", args...) + }) + + t.Run("successful dump", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "blocks") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", outDir, "--count", "5", "--start", "0"} + + e.Run(t, args...) + + require.DirExists(t, outDir) + + for i := range 5 { + blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin") + require.FileExists(t, blockFile) + } + }) + + t.Run("invalid block range", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "invalid-blocks") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", outDir, "--count", "1000", "--start", "0"} + + e.RunWithError(t, args...) + }) + + t.Run("zero blocks (full chain dump)", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "full-dump") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", outDir} + + e.Run(t, args...) + + require.DirExists(t, outDir) + for i := range 5 { + blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin") + require.FileExists(t, blockFile) + } + }) + + t.Run("invalid config file", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "blocks") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", "invalid-config-path", "--out", outDir} + + e.RunWithError(t, args...) + }) +} diff --git a/cli/server/dump_test.go b/cli/server/dump_test.go new file mode 100644 index 0000000..74877e4 --- /dev/null +++ b/cli/server/dump_test.go @@ -0,0 +1,23 @@ +package server + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetPath(t *testing.T) { + testPath := t.TempDir() + actual, err := getPath(testPath, 123) + require.NoError(t, err) + require.Equal(t, filepath.Join(testPath, "BlockStorage_100000", "dump-block-1000.json"), actual) + + actual, err = getPath(testPath, 1230) + require.NoError(t, err) + require.Equal(t, filepath.Join(testPath, "BlockStorage_100000", "dump-block-2000.json"), actual) + + actual, err = getPath(testPath, 123000) + require.NoError(t, err) + require.Equal(t, filepath.Join(testPath, "BlockStorage_200000", "dump-block-123000.json"), actual) +} diff --git a/cli/server/metrics.go b/cli/server/metrics.go new file mode 100644 index 0000000..ddea09d --- /dev/null +++ b/cli/server/metrics.go @@ -0,0 +1,23 @@ +package server + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +var neogoVersion = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Help: "NeoGo version", + Name: "version", + Namespace: "neogo", + }, + []string{"version"}) + +func setNeoGoVersion(nodeVer string) { + neogoVersion.WithLabelValues(nodeVer).Add(1) +} + +func init() { + prometheus.MustRegister( + neogoVersion, + ) +} diff --git a/cli/server/server.go b/cli/server/server.go new file mode 100644 index 0000000..69423c5 --- /dev/null +++ b/cli/server/server.go @@ -0,0 +1,709 @@ +package server + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + stdio "io" + "os" + "os/signal" + "slices" + "syscall" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/consensus" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/chaindump" + corestate "github.com/nspcc-dev/neo-go/pkg/core/stateroot" + "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/network" + "github.com/nspcc-dev/neo-go/pkg/services/metrics" + "github.com/nspcc-dev/neo-go/pkg/services/notary" + "github.com/nspcc-dev/neo-go/pkg/services/oracle" + "github.com/nspcc-dev/neo-go/pkg/services/rpcsrv" + "github.com/nspcc-dev/neo-go/pkg/services/stateroot" + "github.com/urfave/cli/v2" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// NewCommands returns 'node' command. +func NewCommands() []*cli.Command { + cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath} + cfgFlags = append(cfgFlags, options.Network...) + + var cfgWithCountFlags = slices.Clone(cfgFlags) + cfgFlags = append(cfgFlags, options.Debug, options.ForceTimestampLogs) + cfgWithCountFlags = append(cfgWithCountFlags, + &cli.UintFlag{ + Name: "count", + Aliases: []string{"c"}, + Usage: "Number of blocks to be processed (default or 0: all chain)", + }, + ) + var cfgCountOutFlags = slices.Clone(cfgWithCountFlags) + cfgCountOutFlags = append(cfgCountOutFlags, + &cli.UintFlag{ + Name: "start", + Aliases: []string{"s"}, + Usage: "Block number to start from", + }, + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, + Usage: "Output file (stdout if not given)", + }, + ) + var cfgCountInFlags = slices.Clone(cfgWithCountFlags) + cfgCountInFlags = append(cfgCountInFlags, + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Usage: "Input file (stdin if not given)", + }, + &cli.StringFlag{ + Name: "dump", + Usage: "Directory for storing JSON dumps", + }, + ) + var cfgHeightFlags = slices.Clone(cfgFlags) + cfgHeightFlags = append(cfgHeightFlags, &cli.UintFlag{ + Name: "height", + Usage: "Height of the state to reset DB to", + Required: true, + }) + return []*cli.Command{ + { + Name: "node", + Usage: "Start a NeoGo node", + UsageText: "neo-go node [--config-path path] [-d] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]", + Action: startServer, + Flags: cfgFlags, + }, + { + Name: "db", + Usage: "Database manipulations", + Subcommands: []*cli.Command{ + { + Name: "dump", + Usage: "Dump blocks (starting with the genesis or specified block) to the file", + UsageText: "neo-go db dump [-o file] [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]", + Action: dumpDB, + Flags: append(cfgCountOutFlags, + &cli.BoolFlag{ + Name: "non-incremental", + Usage: "Force legacy (non-incremental) dump output", + }, + ), + }, + { + Name: "dump-bin", + Usage: "Dump blocks (starting with the genesis or specified block) to the directory in binary format", + UsageText: "neo-go db dump-bin -o directory [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]", + Action: dumpBin, + Flags: cfgCountOutFlags, + }, + { + Name: "restore", + Usage: "Restore blocks from the file", + UsageText: "neo-go db restore [-i file] [--dump] [-c count] [--config-path path] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]", + Action: restoreDB, + Flags: cfgCountInFlags, + }, + { + Name: "reset", + Usage: "Reset database to the previous state", + UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]", + Action: resetDB, + Flags: cfgHeightFlags, + }, + }, + }, + } +} + +func newGraceContext() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + signal.Notify(stop, syscall.SIGTERM) + go func() { + <-stop + cancel() + }() + return ctx +} + +// InitBCWithMetrics initializes the blockchain with metrics with the given configuration. +func InitBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *metrics.Service, *metrics.Service, error) { + chain, _, err := initBlockChain(cfg, log) + if err != nil { + return nil, nil, nil, cli.Exit(err, 1) + } + prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log) + pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log) + + go chain.Run() + err = prometheus.Start() + if err != nil { + return nil, nil, nil, cli.Exit(fmt.Errorf("failed to start Prometheus service: %w", err), 1) + } + err = pprof.Start() + if err != nil { + return nil, nil, nil, cli.Exit(fmt.Errorf("failed to start Pprof service: %w", err), 1) + } + + return chain, prometheus, pprof, nil +} + +func dumpDB(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return cli.Exit(err, 1) + } + log, _, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration) + if err != nil { + return cli.Exit(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + count := uint32(ctx.Uint("count")) + start := uint32(ctx.Uint("start")) + + var outStream = os.Stdout + if out := ctx.String("out"); out != "" { + outStream, err = os.Create(out) + if err != nil { + return cli.Exit(err, 1) + } + } + defer outStream.Close() + writer := io.NewBinWriterFromIO(outStream) + + chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log) + if err != nil { + return err + } + defer func() { + pprof.ShutDown() + prometheus.ShutDown() + chain.Close() + }() + + chainCount := chain.BlockHeight() + 1 + if start+count > chainCount { + return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainCount-1, count, start), 1) + } + if count == 0 { + count = chainCount - start + } + if start != 0 || !ctx.Bool("non-incremental") { + writer.WriteU32LE(start) + } + writer.WriteU32LE(count) + err = chaindump.Dump(chain, writer, start, count) + if err != nil { + return cli.Exit(err.Error(), 1) + } + return nil +} + +func restoreDB(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return err + } + log, _, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration) + if err != nil { + return cli.Exit(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + count := uint32(ctx.Uint("count")) + + var inStream = os.Stdin + if in := ctx.String("in"); in != "" { + inStream, err = os.Open(in) + if err != nil { + return cli.Exit(err, 1) + } + } + defer inStream.Close() + reader := io.NewBinReaderFromIO(inStream) + + dumpDir := ctx.String("dump") + if dumpDir != "" { + cfg.ApplicationConfiguration.SaveStorageBatch = true + } + + chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log) + if err != nil { + return err + } + defer func() { + pprof.ShutDown() + prometheus.ShutDown() + chain.Close() + }() + + var start = reader.ReadU32LE() + if reader.Err != nil { + return cli.Exit(err, 1) + } + var allBlocks = reader.ReadU32LE() + if reader.Err != nil { + return cli.Exit(err, 1) + } + var versionOrLen = reader.ReadU32LE() + if reader.Err != nil { + return cli.Exit(err, 1) + } + + var buf []byte + if versionOrLen == block.VersionInitial { + // Block length > 0 => we read block version, so we have ordinary chain dump. + buf = binary.LittleEndian.AppendUint32(buf, allBlocks) + allBlocks = start + start = 0 + } + buf = binary.LittleEndian.AppendUint32(buf, versionOrLen) + reader = io.NewBinReaderFromIO(stdio.MultiReader( + bytes.NewReader(buf), + inStream, + )) + + if chain.BlockHeight()+1 < start { + return cli.Exit(fmt.Errorf("expected height: %d, dump starts at %d", + chain.BlockHeight()+1, start), 1) + } + + var skip uint32 + if chain.BlockHeight() != 0 { + skip = chain.BlockHeight() + 1 - start + } + + if skip+count > allBlocks { + return cli.Exit(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1) + } + if count == 0 { + count = allBlocks - skip + } + log.Info("initialize restore", + zap.Uint32("start", start), + zap.Uint32("height", chain.BlockHeight()), + zap.Uint32("skip", skip), + zap.Uint32("count", count)) + + gctx := newGraceContext() + var lastIndex uint32 + dump := newDump() + defer func() { + _ = dump.tryPersist(dumpDir, lastIndex) + }() + + var f = func(b *block.Block) error { + select { + case <-gctx.Done(): + return gctx.Err() + default: + return nil + } + } + if dumpDir != "" { + f = func(b *block.Block) error { + select { + case <-gctx.Done(): + return gctx.Err() + default: + } + batch := chain.LastBatch() + // The genesis block may already be persisted, so LastBatch() will return nil. + if batch == nil && b.Index == 0 { + return nil + } + dump.add(b.Index, batch) + lastIndex = b.Index + if b.Index%1000 == 0 { + if err := dump.tryPersist(dumpDir, b.Index); err != nil { + return fmt.Errorf("can't dump storage to file: %w", err) + } + } + return nil + } + } + + err = chaindump.Restore(chain, reader, skip, count, f) + if err != nil { + return cli.Exit(fmt.Errorf("wrong dump file or settings mismatch: %w", err), 1) + } + return nil +} + +func resetDB(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return cli.Exit(err, 1) + } + h := uint32(ctx.Uint("height")) + + log, _, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration) + if err != nil { + return cli.Exit(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + chain, store, err := initBlockChain(cfg, log) + if err != nil { + return cli.Exit(fmt.Errorf("failed to create Blockchain instance: %w", err), 1) + } + + err = chain.Reset(h) + if err != nil { + return cli.Exit(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1) + } + err = store.Close() + if err != nil { + return cli.Exit(fmt.Errorf("failed to close the DB: %w", err), 1) + } + return nil +} + +// oracleService is an interface representing Oracle service with network.Service +// capabilities and ability to submit oracle responses. +type oracleService interface { + rpcsrv.OracleHandler + network.Service +} + +func mkOracle(config config.OracleConfiguration, magic netmode.Magic, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (oracleService, error) { + if !config.Enabled { + return nil, nil + } + orcCfg := oracle.Config{ + Log: log, + Network: magic, + MainCfg: config, + Chain: chain, + OnTransaction: serv.RelayTxn, + } + orc, err := oracle.NewOracle(orcCfg) + if err != nil { + return nil, fmt.Errorf("can't initialize Oracle module: %w", err) + } + chain.SetOracle(orc) + serv.AddService(orc) + return orc, nil +} + +func mkConsensus(config config.Consensus, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (consensus.Service, error) { + if !config.Enabled { + return nil, nil + } + srv, err := consensus.NewService(consensus.Config{ + Logger: log, + Broadcast: serv.BroadcastExtensible, + Chain: chain, + BlockQueue: serv.GetBlockQueue(), + ProtocolConfiguration: chain.GetConfig().ProtocolConfiguration, + RequestTx: serv.RequestTx, + StopTxFlow: serv.StopTxFlow, + Wallet: config.UnlockWallet, + }) + if err != nil { + return nil, fmt.Errorf("can't initialize Consensus module: %w", err) + } + + serv.AddConsensusService(srv, srv.OnPayload, srv.OnTransaction) + return srv, nil +} + +func mkP2PNotary(config config.P2PNotary, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (*notary.Notary, error) { + if !config.Enabled { + return nil, nil + } + if !chain.P2PSigExtensionsEnabled() { + return nil, errors.New("P2PSigExtensions are disabled, but Notary service is enabled") + } + cfg := notary.Config{ + MainCfg: config, + Chain: chain, + Log: log, + } + n, err := notary.NewNotary(cfg, serv.Net, serv.GetNotaryPool(), func(tx *transaction.Transaction) error { + err := serv.RelayTxn(tx) + if err != nil && !errors.Is(err, core.ErrAlreadyExists) && !errors.Is(err, core.ErrAlreadyInPool) { + return fmt.Errorf("can't relay completed notary transaction: hash %s, error: %w", tx.Hash().StringLE(), err) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to create Notary module: %w", err) + } + serv.AddService(n) + chain.SetNotary(n) + return n, nil +} + +func startServer(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return cli.Exit(err, 1) + } + var logDebug = ctx.Bool("debug") + log, logLevel, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration) + if err != nil { + return cli.Exit(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + + grace, cancel := context.WithCancel(newGraceContext()) + defer cancel() + + serverConfig, err := network.NewServerConfig(cfg) + if err != nil { + return cli.Exit(err, 1) + } + + chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log) + if err != nil { + return cli.Exit(err, 1) + } + defer func() { + pprof.ShutDown() + prometheus.ShutDown() + chain.Close() + }() + + serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log) + if err != nil { + return cli.Exit(fmt.Errorf("failed to create network server: %w", err), 1) + } + srMod := chain.GetStateModule().(*corestate.Module) // Take full responsibility here. + sr, err := stateroot.New(serverConfig.StateRootCfg, srMod, log, chain, serv.BroadcastExtensible) + if err != nil { + return cli.Exit(fmt.Errorf("can't initialize StateRoot service: %w", err), 1) + } + serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload) + + oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log) + if err != nil { + return cli.Exit(err, 1) + } + dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, chain, serv, log) + if err != nil { + return cli.Exit(err, 1) + } + p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log) + if err != nil { + return cli.Exit(err, 1) + } + errChan := make(chan error) + rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan) + serv.AddService(rpcServer) + setNeoGoVersion(config.Version) + serv.Start() + if !cfg.ApplicationConfiguration.RPC.StartWhenSynchronized { + // Run RPC server in a separate routine. This is necessary to avoid a potential + // deadlock: Start() can write errors to errChan which is not yet read in the + // current execution context (see for-loop below). + go rpcServer.Start() + } + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, sighup) + signal.Notify(sigCh, sigusr1) + signal.Notify(sigCh, sigusr2) + + fmt.Fprintln(ctx.App.Writer, Logo()) + fmt.Fprintln(ctx.App.Writer, serv.UserAgent) + fmt.Fprintln(ctx.App.Writer) + + var shutdownErr error +Main: + for { + select { + case err := <-errChan: + shutdownErr = fmt.Errorf("server error: %w", err) + cancel() + case sig := <-sigCh: + var newLogLevel = zapcore.InvalidLevel + + log.Info("signal received", zap.Stringer("name", sig)) + cfgnew, err := options.GetConfigFromContext(ctx) + if err != nil { + log.Warn("can't reread the config file, signal ignored", zap.Error(err)) + break // Continue working. + } + if !cfg.ProtocolConfiguration.Equals(&cfgnew.ProtocolConfiguration) { + log.Warn("ProtocolConfiguration changed, signal ignored") + break // Continue working. + } + if !cfg.ApplicationConfiguration.EqualsButServices(&cfgnew.ApplicationConfiguration) { + log.Warn("ApplicationConfiguration changed in incompatible way, signal ignored") + break // Continue working. + } + if !logDebug && cfgnew.ApplicationConfiguration.LogLevel != cfg.ApplicationConfiguration.LogLevel { + newLogLevel, err = zapcore.ParseLevel(cfgnew.ApplicationConfiguration.LogLevel) + if err != nil { + log.Warn("wrong LogLevel in ApplicationConfiguration, signal ignored", zap.Error(err)) + break // Continue working. + } + } + switch sig { + case sighup: + if newLogLevel != zapcore.InvalidLevel { + logLevel.SetLevel(newLogLevel) + log.Warn("using new logging level", zap.Stringer("level", newLogLevel)) + } + serv.DelService(rpcServer) + rpcServer.Shutdown() + rpcServer = rpcsrv.New(chain, cfgnew.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan) + serv.AddService(rpcServer) + if !cfgnew.ApplicationConfiguration.RPC.StartWhenSynchronized || serv.IsInSync() { + // Here similar to the initial run (see above for-loop), so async. + go rpcServer.Start() + } + pprof.ShutDown() + pprof = metrics.NewPprofService(cfgnew.ApplicationConfiguration.Pprof, log) + err = pprof.Start() + if err != nil { + shutdownErr = fmt.Errorf("failed to start Pprof service: %w", err) + cancel() // Fatal error, like for RPC server. + } + prometheus.ShutDown() + prometheus = metrics.NewPrometheusService(cfgnew.ApplicationConfiguration.Prometheus, log) + err = prometheus.Start() + if err != nil { + shutdownErr = fmt.Errorf("failed to start Prometheus service: %w", err) + cancel() // Fatal error, like for RPC server. + } + case sigusr1: + if oracleSrv != nil { + serv.DelService(oracleSrv) + chain.SetOracle(nil) + rpcServer.SetOracleHandler(nil) + oracleSrv.Shutdown() + } + oracleSrv, err = mkOracle(cfgnew.ApplicationConfiguration.Oracle, cfgnew.ProtocolConfiguration.Magic, chain, serv, log) + if err != nil { + log.Error("failed to create oracle service", zap.Error(err)) + break // Keep going. + } + if oracleSrv != nil { + rpcServer.SetOracleHandler(oracleSrv) + if serv.IsInSync() { + oracleSrv.Start() + } + } + if p2pNotary != nil { + serv.DelService(p2pNotary) + chain.SetNotary(nil) + p2pNotary.Shutdown() + } + p2pNotary, err = mkP2PNotary(cfgnew.ApplicationConfiguration.P2PNotary, chain, serv, log) + if err != nil { + log.Error("failed to create notary service", zap.Error(err)) + break // Keep going. + } + if p2pNotary != nil && serv.IsInSync() { + p2pNotary.Start() + } + serv.DelExtensibleService(sr, stateroot.Category) + srMod.SetUpdateValidatorsCallback(nil) + sr.Shutdown() + sr, err = stateroot.New(cfgnew.ApplicationConfiguration.StateRoot, srMod, log, chain, serv.BroadcastExtensible) + if err != nil { + log.Error("failed to create state validation service", zap.Error(err)) + break // The show must go on. + } + serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload) + if serv.IsInSync() { + sr.Start() + } + case sigusr2: + if dbftSrv != nil { + serv.DelConsensusService(dbftSrv) + dbftSrv.Shutdown() + } + dbftSrv, err = mkConsensus(cfgnew.ApplicationConfiguration.Consensus, chain, serv, log) + if err != nil { + log.Error("failed to create consensus service", zap.Error(err)) + break // Whatever happens, I'll leave it all to chance. + } + if dbftSrv != nil && serv.IsInSync() { + dbftSrv.Start() + } + } + cfg = cfgnew + case <-grace.Done(): + signal.Stop(sigCh) + serv.Shutdown() + break Main + } + } + + if shutdownErr != nil { + return cli.Exit(shutdownErr, 1) + } + + return nil +} + +// initBlockChain initializes BlockChain with preselected DB. +func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, error) { + store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration) + if err != nil { + return nil, nil, cli.Exit(fmt.Errorf("could not initialize storage: %w", err), 1) + } + + chain, err := core.NewBlockchain(store, cfg.Blockchain(), log) + if err != nil { + errText := "could not initialize blockchain: %w" + errArgs := []any{err} + closeErr := store.Close() + if closeErr != nil { + errText += "; failed to close the DB: %w" + errArgs = append(errArgs, closeErr) + } + + return nil, nil, cli.Exit(fmt.Errorf(errText, errArgs...), 1) + } + return chain, store, nil +} + +// Logo returns NeoGo logo. +func Logo() string { + return ` + _ ____________ __________ + / | / / ____/ __ \ / ____/ __ \ + / |/ / __/ / / / /_____/ / __/ / / / + / /| / /___/ /_/ /_____/ /_/ / /_/ / +/_/ |_/_____/\____/ \____/\____/ +` +} diff --git a/cli/server/server_test.go b/cli/server/server_test.go new file mode 100644 index 0000000..6c44784 --- /dev/null +++ b/cli/server/server_test.go @@ -0,0 +1,408 @@ +package server + +import ( + "encoding/binary" + "flag" + "os" + "path/filepath" + "runtime" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" + "gopkg.in/yaml.v3" +) + +// serverTestWD is the default working directory for server tests. +var serverTestWD string + +func init() { + var err error + serverTestWD, err = os.Getwd() + if err != nil { + panic("can't get current working directory") + } +} + +func TestGetConfigFromContext(t *testing.T) { + t.Run("config-path", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", "../../config", "") + set.Bool("testnet", true, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + cfg, err := options.GetConfigFromContext(ctx) + require.NoError(t, err) + require.Equal(t, netmode.TestNet, cfg.ProtocolConfiguration.Magic) + }) + t.Run("config-file", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", "../../config", "") + set.Bool("testnet", true, "") + set.String("config-file", "../../config/protocol.testnet.yml", "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + cfg, err := options.GetConfigFromContext(ctx) + require.NoError(t, err) + require.Equal(t, netmode.TestNet, cfg.ProtocolConfiguration.Magic) + }) + t.Run("relative-path windows", func(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("skipping Windows specific test") + } + + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("relative-path", "..\\..\\config", "") + set.Bool("testnet", true, "") + set.String("config-file", ".\\testdata\\protocol.testnet.windows.yml", "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + cfg, err := options.GetConfigFromContext(ctx) + require.NoError(t, err) + require.Equal(t, filepath.Join("..", "..", "config", "chains", "testnet"), cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath) + require.Equal(t, "C:\\someFolder\\cn_wallet.json", cfg.ApplicationConfiguration.Consensus.UnlockWallet.Path) + require.Equal(t, "C:\\someFolder\\notary_wallet.json", cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path) + }) + + t.Run("relative-path non-windows", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping non-Windows specific test") + } + + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("relative-path", "../../config", "") + set.Bool("testnet", true, "") + set.String("config-file", "../../config/protocol.testnet.yml", "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + cfg, err := options.GetConfigFromContext(ctx) + require.NoError(t, err) + require.Equal(t, filepath.Join("..", "..", "config", "chains", "testnet"), cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath) + require.Equal(t, "/cn_wallet.json", cfg.ApplicationConfiguration.Consensus.UnlockWallet.Path) + require.Equal(t, "/notary_wallet.json", cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path) + }) +} + +func TestHandleLoggingParams(t *testing.T) { + d := t.TempDir() + testLog := filepath.Join(d, "file.log") + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + debug := set.Bool("debug", false, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + + t.Run("logdir is a file", func(t *testing.T) { + logfile := filepath.Join(d, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg := config.ApplicationConfiguration{ + Logger: config.Logger{ + LogPath: filepath.Join(logfile, "file.log"), + }, + } + _, lvl, closer, err := options.HandleLoggingParams(ctx, cfg) + require.Error(t, err) + require.Nil(t, lvl) + require.Nil(t, closer) + }) + + t.Run("broken level", func(t *testing.T) { + cfg := config.ApplicationConfiguration{ + Logger: config.Logger{ + LogPath: testLog, + LogLevel: "qwerty", + }, + } + _, lvl, closer, err := options.HandleLoggingParams(ctx, cfg) + require.Error(t, err) + require.Nil(t, lvl) + require.Nil(t, closer) + }) + + t.Run("default", func(t *testing.T) { + cfg := config.ApplicationConfiguration{ + Logger: config.Logger{ + LogPath: testLog, + }, + } + logger, lvl, closer, err := options.HandleLoggingParams(ctx, cfg) + require.NotNil(t, lvl) + require.NoError(t, err) + t.Cleanup(func() { + if closer != nil { + require.NoError(t, closer()) + } + }) + require.Equal(t, zapcore.InfoLevel, lvl.Level()) + require.True(t, logger.Core().Enabled(zapcore.InfoLevel)) + require.False(t, logger.Core().Enabled(zapcore.DebugLevel)) + }) + + t.Run("warn", func(t *testing.T) { + cfg := config.ApplicationConfiguration{ + Logger: config.Logger{ + LogPath: testLog, + LogLevel: "warn", + }, + } + logger, lvl, closer, err := options.HandleLoggingParams(ctx, cfg) + require.NoError(t, err) + t.Cleanup(func() { + if closer != nil { + require.NoError(t, closer()) + } + }) + require.Equal(t, zapcore.WarnLevel, lvl.Level()) + require.True(t, logger.Core().Enabled(zapcore.WarnLevel)) + require.False(t, logger.Core().Enabled(zapcore.InfoLevel)) + }) + + t.Run("debug", func(t *testing.T) { + cfg := config.ApplicationConfiguration{ + Logger: config.Logger{ + LogPath: testLog, + }, + } + *debug = true + t.Cleanup(func() { *debug = false }) + logger, lvl, closer, err := options.HandleLoggingParams(ctx, cfg) + require.NoError(t, err) + t.Cleanup(func() { + if closer != nil { + require.NoError(t, closer()) + } + }) + require.Equal(t, zapcore.DebugLevel, lvl.Level()) + require.True(t, logger.Core().Enabled(zapcore.InfoLevel)) + require.True(t, logger.Core().Enabled(zapcore.DebugLevel)) + }) +} + +func TestInitBCWithMetrics(t *testing.T) { + d := t.TempDir() + err := os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) + + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "") + set.Bool("testnet", true, "") + set.Bool("debug", true, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + cfg, err := options.GetConfigFromContext(ctx) + require.NoError(t, err) + logger, _, closer, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration) + require.NoError(t, err) + t.Cleanup(func() { + if closer != nil { + require.NoError(t, closer()) + } + }) + + t.Run("bad store", func(t *testing.T) { + _, _, _, err = InitBCWithMetrics(config.Config{}, logger) + require.Error(t, err) + }) + + chain, prometheus, pprof, err := InitBCWithMetrics(cfg, logger) + require.NoError(t, err) + t.Cleanup(func() { + chain.Close() + prometheus.ShutDown() + pprof.ShutDown() + }) + require.Equal(t, netmode.TestNet, chain.GetConfig().Magic) +} + +func TestDumpDB(t *testing.T) { + testDump := "file.acc" + + t.Run("too low chain", func(t *testing.T) { + d := t.TempDir() + err := os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "") + set.Bool("privnet", true, "") + set.Bool("debug", true, "") + set.Int("start", 0, "") + set.Int("count", 5, "") + set.String("out", testDump, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + err = dumpDB(ctx) + require.Error(t, err) + }) + + t.Run("positive", func(t *testing.T) { + d := t.TempDir() + err := os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "") + set.Bool("privnet", true, "") + set.Bool("debug", true, "") + set.Int("start", 0, "") + set.Int("count", 1, "") + set.String("out", testDump, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + err = dumpDB(ctx) + require.NoError(t, err) + }) +} + +func TestRestoreDB(t *testing.T) { + d := t.TempDir() + testDump := "file1.acc" + saveDump := "file2.acc" + err := os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) + + // dump first + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + goodCfg := filepath.Join(serverTestWD, "..", "..", "config") + cfgPath := set.String("config-path", goodCfg, "") + set.Bool("privnet", true, "") + set.Bool("debug", true, "") + set.Bool("non-incremental", true, "") + set.Int("start", 0, "") + set.Int("count", 1, "") + set.String("out", testDump, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + err = dumpDB(ctx) + require.NoError(t, err) + + // and then restore + t.Run("invalid config", func(t *testing.T) { + *cfgPath = filepath.Join(serverTestWD, "..", "..", "config_invalid") + require.Error(t, restoreDB(ctx)) + }) + t.Run("invalid logger path", func(t *testing.T) { + badCfgDir := t.TempDir() + logfile := filepath.Join(badCfgDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + badCfgPath := filepath.Join(badCfgDir, "protocol.privnet.yml") + require.NoError(t, os.WriteFile(badCfgPath, out, os.ModePerm)) + + *cfgPath = badCfgDir + require.Error(t, restoreDB(ctx)) + + *cfgPath = goodCfg + }) + t.Run("invalid bc config", func(t *testing.T) { + badCfgDir := t.TempDir() + cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = "" + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + badCfgPath := filepath.Join(badCfgDir, "protocol.privnet.yml") + require.NoError(t, os.WriteFile(badCfgPath, out, os.ModePerm)) + + *cfgPath = badCfgDir + require.Error(t, restoreDB(ctx)) + + *cfgPath = goodCfg + }) + + in := set.String("in", testDump, "") + incremental := set.Bool("incremental", false, "") + t.Run("invalid in", func(t *testing.T) { + *in = "unknown-file" + require.Error(t, restoreDB(ctx)) + + *in = testDump + }) + t.Run("corrupted in: invalid block count", func(t *testing.T) { + inPath := filepath.Join(t.TempDir(), "file3.acc") + require.NoError(t, os.WriteFile(inPath, []byte{1, 2, 3}, // file is expected to start from uint32 + os.ModePerm)) + *in = inPath + require.Error(t, restoreDB(ctx)) + + *in = testDump + }) + t.Run("corrupted in: corrupted block", func(t *testing.T) { + inPath := filepath.Join(t.TempDir(), "file3.acc") + b, err := os.ReadFile(testDump) + require.NoError(t, err) + b[5] = 0xff // file is expected to start from uint32 (4 bytes) followed by the first block, so corrupt the first block bytes + require.NoError(t, os.WriteFile(inPath, b, os.ModePerm)) + *in = inPath + require.Error(t, restoreDB(ctx)) + + *in = testDump + }) + t.Run("incremental dump", func(t *testing.T) { + inPath := filepath.Join(t.TempDir(), "file1_incremental.acc") + b, err := os.ReadFile(testDump) + require.NoError(t, err) + start := make([]byte, 4) + t.Run("good", func(t *testing.T) { + binary.LittleEndian.PutUint32(start, 1) // start from the first block + require.NoError(t, os.WriteFile(inPath, append(start, b...), + os.ModePerm)) + *in = inPath + *incremental = true + + require.NoError(t, restoreDB(ctx)) + }) + t.Run("dump is too high", func(t *testing.T) { + binary.LittleEndian.PutUint32(start, 2) // start from the second block + require.NoError(t, os.WriteFile(inPath, append(start, b...), + os.ModePerm)) + *in = inPath + *incremental = true + + require.Error(t, restoreDB(ctx)) + }) + + *in = testDump + *incremental = false + }) + + set.String("dump", saveDump, "") + require.NoError(t, restoreDB(ctx)) +} + +func TestInitBlockChain(t *testing.T) { + t.Run("bad storage", func(t *testing.T) { + _, _, err := initBlockChain(config.Config{}, nil) + require.Error(t, err) + }) + + t.Run("empty logger", func(t *testing.T) { + _, _, err := initBlockChain(config.Config{ + ApplicationConfiguration: config.ApplicationConfiguration{ + DBConfiguration: dbconfig.DBConfiguration{ + Type: dbconfig.InMemoryDB, + }, + }, + }, nil) + require.Error(t, err) + }) +} + +func TestResetDB(t *testing.T) { + d := t.TempDir() + err := os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "") + set.Bool("privnet", true, "") + set.Bool("debug", true, "") + set.Int("height", 0, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + err = resetDB(ctx) + require.NoError(t, err) +} diff --git a/cli/server/signals_unix.go b/cli/server/signals_unix.go new file mode 100644 index 0000000..ef9135b --- /dev/null +++ b/cli/server/signals_unix.go @@ -0,0 +1,11 @@ +//go:build !windows + +package server + +import "syscall" + +const ( + sighup = syscall.SIGHUP + sigusr1 = syscall.SIGUSR1 + sigusr2 = syscall.SIGUSR2 +) diff --git a/cli/server/signals_windows.go b/cli/server/signals_windows.go new file mode 100644 index 0000000..30a7033 --- /dev/null +++ b/cli/server/signals_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +package server + +import "syscall" + +const ( + // Doesn't really matter, Windows can't do it. + sighup = syscall.SIGHUP + sigusr1 = syscall.Signal(0xa) + sigusr2 = syscall.Signal(0xc) +) diff --git a/cli/smartcontract/contract_test.go b/cli/smartcontract/contract_test.go new file mode 100644 index 0000000..725fe39 --- /dev/null +++ b/cli/smartcontract/contract_test.go @@ -0,0 +1,1448 @@ +package smartcontract_test + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/cli/smartcontract" + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/internal/testchain" + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/internal/versionutil" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// Keep contract NEFs consistent between runs. +const _ = versionutil.TestVersion + +func TestCalcHash(t *testing.T) { + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + nefPath := "./testdata/verify.nef" + src, err := os.ReadFile(nefPath) + require.NoError(t, err) + nefF, err := nef.FileFromBytes(src) + require.NoError(t, err) + manifestPath := "./testdata/verify.manifest.json" + manifestBytes, err := os.ReadFile(manifestPath) + require.NoError(t, err) + manif := &manifest.Manifest{} + err = json.Unmarshal(manifestBytes, manif) + require.NoError(t, err) + sender := random.Uint160() + + cmd := []string{"neo-go", "contract", "calc-hash"} + t.Run("no sender", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "sender" not set`, append(cmd, "--in", nefPath, "--manifest", manifestPath)...) + }) + t.Run("no nef file", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "in" not set`, append(cmd, "--sender", sender.StringLE(), "--manifest", manifestPath)...) + }) + t.Run("no manifest file", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "manifest" not set`, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...) + }) + t.Run("invalid nef path", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), + "--in", "./testdata/verify.nef123", "--manifest", manifestPath)...) + }) + t.Run("invalid manifest path", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), + "--in", nefPath, "--manifest", "./testdata/verify.manifest123")...) + }) + t.Run("invalid nef file", func(t *testing.T) { + p := filepath.Join(tmpDir, "neogo.calchash.verify.nef") + require.NoError(t, os.WriteFile(p, src[:4], os.ModePerm)) + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", p, "--manifest", manifestPath)...) + }) + t.Run("invalid manifest file", func(t *testing.T) { + p := filepath.Join(tmpDir, "neogo.calchash.verify.manifest.json") + require.NoError(t, os.WriteFile(p, manifestBytes[:4], os.ModePerm)) + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", nefPath, "--manifest", p)...) + }) + + cmd = append(cmd, "--in", nefPath, "--manifest", manifestPath) + expected := state.CreateContractHash(sender, nefF.Checksum, manif.Name) + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "something")...) + }) + t.Run("valid, uint160", func(t *testing.T) { + e.Run(t, append(cmd, "--sender", sender.StringLE())...) + e.CheckNextLine(t, expected.StringLE()) + }) + t.Run("valid, uint160 with 0x", func(t *testing.T) { + e.Run(t, append(cmd, "--sender", "0x"+sender.StringLE())...) + e.CheckNextLine(t, expected.StringLE()) + }) + t.Run("valid, address", func(t *testing.T) { + e.Run(t, append(cmd, "--sender", address.Uint160ToString(sender))...) + e.CheckNextLine(t, expected.StringLE()) + }) +} + +func TestContractBindings(t *testing.T) { + // For proper contract init. The actual version as it will be replaced. + smartcontract.ModVersion = "v0.0.0" + + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + ctrPath := filepath.Join(tmpDir, "testcontract") + e.Run(t, "neo-go", "contract", "init", "--name", ctrPath) + + srcPath := filepath.Join(ctrPath, "main.go") + require.NoError(t, os.WriteFile(srcPath, []byte(`package testcontract +import( + alias "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" +) +type MyPair struct { + Key int + Value string +} +func ToMap(a []MyPair) map[int]string { + return nil +} +func ToArray(m map[int]string) []MyPair { + return nil +} +func Block() *alias.Block{ + return alias.GetBlock(1) +} +func Blocks() []*alias.Block { + return []*alias.Block{ + alias.GetBlock(10), + alias.GetBlock(11), + } +} +`), os.ModePerm)) + + cfgPath := filepath.Join(ctrPath, "neo-go.yml") + manifestPath := filepath.Join(tmpDir, "manifest.json") + bindingsPath := filepath.Join(tmpDir, "bindings.yml") + cmd := []string{"neo-go", "contract", "compile"} + + cmd = append(cmd, "--in", ctrPath, "--bindings", bindingsPath) + + // Replace `pkg/interop` in go.mod to avoid getting an actual module version. + require.NoError(t, updateGoMod(ctrPath, "myimport.com/testcontract", "../../pkg/interop")) + + cmd = append(cmd, "--config", cfgPath, + "--out", filepath.Join(tmpDir, "out.nef"), + "--manifest", manifestPath, + "--bindings", bindingsPath) + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, append(cmd, "something")...) + }) + e.Run(t, cmd...) + e.CheckEOF(t) + require.FileExists(t, bindingsPath) + + outPath := filepath.Join(t.TempDir(), "binding.go") + e.Run(t, "neo-go", "contract", "generate-wrapper", + "--config", bindingsPath, "--manifest", manifestPath, + "--out", outPath, "--hash", "0x0123456789987654321001234567899876543210") + + bs, err := os.ReadFile(outPath) + require.NoError(t, err) + require.Equal(t, `// Code generated by neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package testcontract contains wrappers for testcontract contract. +package testcontract + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" + "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + "myimport.com/testcontract" +) + +// Hash contains contract hash in big-endian form. +const Hash = "\x10\x32\x54\x76\x98\x89\x67\x45\x23\x01\x10\x32\x54\x76\x98\x89\x67\x45\x23\x01" + +// Block invokes `+"`block`"+` method of contract. +func Block() *ledger.Block { + return neogointernal.CallWithToken(Hash, "block", int(contract.All)).(*ledger.Block) +} + +// Blocks invokes `+"`blocks`"+` method of contract. +func Blocks() []*ledger.Block { + return neogointernal.CallWithToken(Hash, "blocks", int(contract.All)).([]*ledger.Block) +} + +// ToArray invokes `+"`toArray`"+` method of contract. +func ToArray(m map[int]string) []testcontract.MyPair { + return neogointernal.CallWithToken(Hash, "toArray", int(contract.All), m).([]testcontract.MyPair) +} + +// ToMap invokes `+"`toMap`"+` method of contract. +func ToMap(a []testcontract.MyPair) map[int]string { + return neogointernal.CallWithToken(Hash, "toMap", int(contract.All), a).(map[int]string) +} +`, string(bs)) +} + +// updateGoMod updates the go.mod file located in the specified directory. +// It sets the module name and replaces the neo-go interop package path with +// the provided one to avoid getting an actual module version. +func updateGoMod(dir, moduleName, neoGoPath string) error { + goModPath := filepath.Join(dir, "go.mod") + data, err := os.ReadFile(goModPath) + if err != nil { + return fmt.Errorf("failed to read go.mod: %w", err) + } + + i := bytes.IndexByte(data, '\n') + if i == -1 { + return fmt.Errorf("unexpected go.mod format") + } + + updatedData := append([]byte("module "+moduleName), data[i:]...) + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get working directory: %w", err) + } + + replacementPath := filepath.Join(wd, neoGoPath) + updatedData = append(updatedData, "\nreplace github.com/nspcc-dev/neo-go/pkg/interop => "+replacementPath+" \n"...) + + if err := os.WriteFile(goModPath, updatedData, os.ModePerm); err != nil { + return fmt.Errorf("failed to write updated go.mod: %w", err) + } + + return nil +} + +func TestDynamicWrapper(t *testing.T) { + // For proper contract init. The actual version as it will be replaced. + smartcontract.ModVersion = "v0.0.0" + + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, true) + + ctrPath := "../smartcontract/testdata" + + verifyHash := testcli.DeployContract(t, e, filepath.Join(ctrPath, "verify.go"), filepath.Join(ctrPath, "verify.yml"), testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass) + + helperContract := `package testcontract + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + verify "myimport.com/testcontract/bindings" +) + +func CallVerifyContract(h interop.Hash160) bool{ + contractInstance := verify.NewContract(h) + return contractInstance.Verify() +}` + + helperDir := filepath.Join(tmpDir, "helper") + e.Run(t, "neo-go", "contract", "init", "--name", helperDir) + + require.NoError(t, updateGoMod(helperDir, "myimport.com/testcontract", "../../pkg/interop")) + require.NoError(t, os.WriteFile(filepath.Join(helperDir, "main.go"), []byte(helperContract), os.ModePerm)) + require.NoError(t, os.Mkdir(filepath.Join(helperDir, "bindings"), os.ModePerm)) + + e.Run(t, "neo-go", "contract", "generate-wrapper", + "--config", filepath.Join(ctrPath, "verify.bindings.yml"), "--manifest", filepath.Join(ctrPath, "verify.manifest.json"), + "--out", filepath.Join(helperDir, "bindings", "testdata.go")) + e.Run(t, "neo-go", "contract", "compile", "--in", filepath.Join(helperDir, "main.go"), "--config", filepath.Join(helperDir, "neo-go.yml")) + helperHash := testcli.DeployContract(t, e, filepath.Join(helperDir, "main.go"), filepath.Join(helperDir, "neo-go.yml"), testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass) + + e.In.WriteString("one\r") + e.Run(t, "neo-go", "contract", "invokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--force", "--await", helperHash.StringLE(), "callVerifyContract", verifyHash.StringLE()) + + tx, _ := e.CheckTxPersisted(t, "Sent invocation transaction ") + aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, aer[0].Stack[0].Value().(bool), true) +} + +func TestContractInitAndCompile(t *testing.T) { + // For proper contract init. The actual version as it will be replaced. + smartcontract.ModVersion = "v0.0.0" + + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + t.Run("no path is provided", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "name" not set`, "neo-go", "contract", "init") + }) + t.Run("invalid path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "init", "--name", "\x00") + }) + + ctrPath := filepath.Join(tmpDir, "testcontract") + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "init", "--name", ctrPath, "something") + }) + + e.Run(t, "neo-go", "contract", "init", "--name", ctrPath) + + t.Run("don't rewrite existing directory", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "init", "--name", ctrPath) + }) + + ctrRootPath := filepath.Join(ctrPath, "main") + srcPath := ctrRootPath + ".go" + cfgPath := filepath.Join(ctrPath, "neo-go.yml") + nefPath := filepath.Join(tmpDir, "testcontract.nef") + manifestPath := filepath.Join(tmpDir, "testcontract.manifest.json") + cmd := []string{"neo-go", "contract", "compile"} + t.Run("missing source", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "in" not set`, cmd...) + }) + + cmd = append(cmd, "--in", srcPath, "--out", nefPath, "--manifest", manifestPath) + t.Run("missing config, but require manifest", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) + t.Run("provided non-existent config", func(t *testing.T) { + cfgName := filepath.Join(ctrPath, "notexists.yml") + e.RunWithError(t, append(cmd, "--config", cfgName)...) + }) + t.Run("provided corrupted config", func(t *testing.T) { + data, err := os.ReadFile(cfgPath) + require.NoError(t, err) + badCfg := filepath.Join(tmpDir, "bad.yml") + require.NoError(t, os.WriteFile(badCfg, data[:len(data)-5], os.ModePerm)) + e.RunWithError(t, append(cmd, "--config", badCfg)...) + }) + + // Replace `pkg/interop` in go.mod to avoid getting an actual module version. + require.NoError(t, updateGoMod(ctrPath, "myimport.com/testcontract", "../../pkg/interop")) + + cmd = append(cmd, "--config", cfgPath) + + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, append(cmd, "something")...) + }) + + e.Run(t, cmd...) + e.CheckEOF(t) + require.FileExists(t, nefPath) + require.FileExists(t, manifestPath) + + t.Run("output hex script with --verbose", func(t *testing.T) { + e.Run(t, append(cmd, "--verbose")...) + e.CheckNextLine(t, "^[0-9a-hA-H]+$") + }) + + t.Run("autocomplete outputs", func(t *testing.T) { + cfg, err := os.ReadFile(cfgPath) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(ctrPath, "main.yml"), cfg, os.ModePerm)) + e.Run(t, "neo-go", "contract", "compile", "--in", srcPath) + defaultNefPath := ctrRootPath + ".nef" + defaultManifestPath := ctrRootPath + ".manifest.json" + defaultBindingsPath := ctrRootPath + ".bindings.yml" + require.FileExists(t, defaultNefPath) + require.FileExists(t, defaultManifestPath) + require.FileExists(t, defaultBindingsPath) + }) +} + +// Checks that error is returned if GAS available for test-invoke exceeds +// GAS needed to be consumed. +func TestDeployBigContract(t *testing.T) { + e := testcli.NewExecutorWithConfig(t, true, true, func(c *config.Config) { + c.ApplicationConfiguration.RPC.MaxGasInvoke = fixedn.Fixed8(1) + }) + tmpDir := t.TempDir() + + nefName := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + e.In.WriteString(testcli.ValidatorPass + "\r") + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName) +} + +func TestContractDeployWithData(t *testing.T) { + eCompile := testcli.NewExecutor(t, false) + tmpDir := t.TempDir() + + nefName := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + eCompile.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + deployContract := func(t *testing.T, haveData bool, scope string, await bool) { + e := testcli.NewExecutor(t, true) + cmd := []string{ + "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "--force", + } + + if await { + cmd = append(cmd, "--await") + } + if haveData { + cmd = append(cmd, "[", "key1", "12", "key2", "take_me_to_church", "]") + } + if scope != "" { + cmd = append(cmd, "--", testcli.ValidatorAddr+":"+scope) + } else { + scope = "CalledByEntry" + } + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, cmd...) + var tx *transaction.Transaction + if await { + tx, _ = e.CheckAwaitableTxPersisted(t) + } else { + tx, _ = e.CheckTxPersisted(t) + } + + require.Equal(t, scope, tx.Signers[0].Scopes.String()) + if !haveData { + return + } + + line, err := e.Out.ReadString('\n') + require.NoError(t, err) + line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) + h, err := util.Uint160DecodeStringLE(line) + require.NoError(t, err) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + h.StringLE(), + "getValueWithKey", "key1", + ) + + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, []byte{12}, res.Stack[0].Value()) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + h.StringLE(), + "getValueWithKey", "key2", + ) + + res = new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value()) + } + + deployContract(t, true, "", false) + deployContract(t, false, "Global", false) + deployContract(t, true, "Global", false) + deployContract(t, false, "", true) + deployContract(t, true, "Global", true) + deployContract(t, true, "", true) +} + +func TestDeployWithSigners(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + + nefName := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + t.Run("missing nef", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName) + }) + t.Run("missing manifest", func(t *testing.T) { + e.RunWithErrorCheck(t, "required flag --manifest is empty", "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", "") + }) + t.Run("corrupted data", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "[", "str1") + }) + t.Run("invalid data", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "str1", "str2") + }) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "[", "str1", "str2", "]") + }) + t.Run("missing RPC", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "contract", "deploy", + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "[", "str1", "str2", "]") + }) + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "--force", + "--", testcli.ValidatorAddr+":Global") + tx, _ := e.CheckTxPersisted(t, "Sent invocation transaction ") + require.Equal(t, transaction.Global, tx.Signers[0].Scopes) +} + +func TestContractUpdate(t *testing.T) { + tmp := t.TempDir() + nefA := filepath.Join(tmp, "a.nef") + mfA := filepath.Join(tmp, "a.manifest.json") + nefB := filepath.Join(tmp, "b.nef") + mfB := filepath.Join(tmp, "b.manifest.json") + + e := testcli.NewExecutor(t, true) + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", + "--config", "testdata/deploy/neo-go.yml", + "--out", nefA, "--manifest", mfA) + + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/updated.go", + "--config", "testdata/deploy/neo-go.yml", + "--out", nefB, "--manifest", mfB) + + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", testcli.ValidatorAddr, + "--in", nefA, "--manifest", mfA, + "--force", "--await", + ) + _, _ = e.CheckAwaitableTxPersisted(t) + line, err := e.Out.ReadString('\n') + require.NoError(t, err) + line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) + hash, err := util.Uint160DecodeStringLE(line) + require.NoError(t, err) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + hash.StringLE(), + "getValue", + ) + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, string(res.Stack[0].Value().([]byte)), "on create|sub create") + + t.Run("missing hash", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "no smart contract hash was provided, specify one as the first argument", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", nefA, "--manifest", mfA, + ) + }) + + t.Run("invalid hash", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "invalid contract hash", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", nefA, "--manifest", mfA, + "badhex", + ) + }) + + t.Run("missing nef and manifest files", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "either manifest or .nef required", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), + ) + }) + + t.Run("malformed data param", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "unable to parse 'data' parameter: ", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", nefA, "--manifest", mfA, + testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), + "[", + ) + }) + + t.Run("too many data params", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "'data' should be represented as a single parameter", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", nefA, "--manifest", mfA, + testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), + "one", "two", + ) + }) + + t.Run("missing wallet", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "can't get sender address: ", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", nefA, "--manifest", mfA, + testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), + ) + }) + + t.Run("invalid signer", func(t *testing.T) { + e.In.WriteString(testcli.ValidatorPass + "\r") + e.RunWithErrorCheckExit(t, "failed to parse signer", + "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--in", nefB, "--manifest", mfB, + "--force", "--await", + hash.StringLE(), + "--", "badSigner", + ) + }) + + t.Run("good with only manifest", func(t *testing.T) { + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--manifest", "testdata/deploy/update.manifest.json", + "--force", "--await", + hash.StringLE(), + ) + _, _ = e.CheckAwaitableTxPersisted(t) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + hash.StringLE(), + "getValueUpdated", + ) + res2 := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res2)) + require.Equal(t, vmstate.Halt.String(), res2.State, res2.FaultException) + require.Len(t, res2.Stack, 1) + require.Equal(t, + "on update|sub update", + string(res2.Stack[0].Value().([]byte)), + ) + }) + + t.Run("good", func(t *testing.T) { + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "update", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--in", nefB, "--manifest", mfB, + "--force", "--await", + hash.StringLE(), + ) + _, _ = e.CheckAwaitableTxPersisted(t) + require.NoError(t, err) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + hash.StringLE(), + "newMethod", + ) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, int64(42), res.Stack[0].Value().(*big.Int).Int64()) + }) +} + +func TestContractDestroy(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + + nefName := filepath.Join(tmpDir, "destroy.nef") + manifestName := filepath.Join(tmpDir, "destroy.manifest.json") + + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName, + ) + + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName, + "--force", "--await", + ) + _, _ = e.CheckAwaitableTxPersisted(t) + + line, err := e.Out.ReadString('\n') + require.NoError(t, err) + line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) + hash, err := util.Uint160DecodeStringLE(line) + require.NoError(t, err) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + hash.StringLE(), + "getValue", + ) + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, string(res.Stack[0].Value().([]byte)), "on create|sub create") + + t.Run("missing hash", func(t *testing.T) { + e.RunWithErrorCheckExit(t, + "no smart contract hash was provided, specify one as the first argument", + "neo-go", "contract", "destroy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + ) + }) + + t.Run("invalid hash", func(t *testing.T) { + e.RunWithErrorCheckExit(t, + "invalid contract hash", + "neo-go", "contract", "destroy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "badhex", + ) + }) + + t.Run("missing wallet", func(t *testing.T) { + e.RunWithErrorCheckExit(t, + "can't get sender address:", + "neo-go", "contract", "destroy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), + ) + }) + + t.Run("invalid signer", func(t *testing.T) { + e.In.WriteString(testcli.ValidatorPass + "\r") + e.RunWithErrorCheckExit(t, + "failed to parse signer", + "neo-go", "contract", "destroy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--force", + testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), + "--", + "badSigner", + ) + }) + + t.Run("good", func(t *testing.T) { + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "destroy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--force", "--await", + hash.StringLE(), + ) + + _, _ = e.CheckAwaitableTxPersisted(t) + require.NoError(t, err) + + e.RunWithErrorCheckExit(t, + fmt.Sprintf("System.Contract.Call failed: called contract %s not found: key not found", hash.StringLE()), + "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + hash.StringLE(), "getValue", + ) + }) +} + +func TestContractManifestGroups(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + + _, err := wallet.NewWalletFromFile(testcli.TestWalletPath) + require.NoError(t, err) + + nefName := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + t.Run("missing wallet", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flags "sender, address, nef, manifest" not set`, "neo-go", "contract", "manifest", "add-group") + }) + t.Run("invalid wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", t.TempDir(), "--sender", testcli.TestWalletAccount, "--address", testcli.TestWalletAccount, + "--nef", nefName, "--manifest", manifestName) + }) + t.Run("invalid sender", func(t *testing.T) { + e.RunWithErrorCheck(t, `invalid value "not-a-sender" for flag -sender: invalid base58 digit ('-')`, "neo-go", "contract", "manifest", "add-group", + "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, + "--sender", "not-a-sender", "--nef", nefName, "--manifest", manifestName) + }) + t.Run("invalid NEF file", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, + "--sender", testcli.TestWalletAccount, "--nef", tmpDir, "--manifest", manifestName) + }) + t.Run("corrupted NEF file", func(t *testing.T) { + f := filepath.Join(tmpDir, "invalid.nef") + require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, + "--sender", testcli.TestWalletAccount, "--nef", f, "--manifest", manifestName) + }) + t.Run("invalid manifest file", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, + "--sender", testcli.TestWalletAccount, "--nef", nefName, + "--manifest", tmpDir) + }) + t.Run("corrupted manifest file", func(t *testing.T) { + f := filepath.Join(tmpDir, "invalid.manifest.json") + require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, + "--sender", testcli.TestWalletAccount, "--nef", nefName, + "--manifest", f) + }) + t.Run("unknown account", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testcli.TestWalletPath, "--address", util.Uint160{}.StringLE(), + "--sender", testcli.TestWalletAccount, "--nef", nefName, + "--manifest", manifestName) + }) + cmd := []string{"neo-go", "contract", "manifest", "add-group", + "--nef", nefName, "--manifest", manifestName} + + t.Run("excessive parameters", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--wallet", testcli.TestWalletPath, + "--sender", testcli.TestWalletAccount, "--address", testcli.TestWalletAccount, "something")...) + }) + e.In.WriteString("testpass\r") + e.Run(t, append(cmd, "--wallet", testcli.TestWalletPath, + "--sender", testcli.TestWalletAccount, "--address", testcli.TestWalletAccount)...) + + e.In.WriteString("testpass\r") // should override signature with the previous sender + e.Run(t, append(cmd, "--wallet", testcli.TestWalletPath, + "--sender", testcli.ValidatorAddr, "--address", testcli.TestWalletAccount)...) + + e.In.WriteString(testcli.ValidatorPass + "\r") + e.Run(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", nefName, "--manifest", manifestName, + "--force", + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr) +} + +func deployVerifyContract(t *testing.T, e *testcli.Executor) util.Uint160 { + return testcli.DeployContract(t, e, "testdata/verify.go", "testdata/verify.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass) +} + +func TestContract_TestInvokeScript(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + badNef := filepath.Join(tmpDir, "invalid.nef") + goodNef := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", goodNef, "--manifest", manifestName) + + t.Run("missing in", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) + }) + t.Run("empty in", func(t *testing.T) { + e.RunWithErrorCheck(t, "required flag --in is empty", "neo-go", "contract", "testinvokescript", "-i", "", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) + }) + t.Run("empty rpc", func(t *testing.T) { + e.RunWithErrorCheck(t, "required flag --rpc-endpoint is empty", "neo-go", "contract", "testinvokescript", "-i", goodNef, + "--rpc-endpoint", "") + }) + t.Run("unexisting in", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", badNef) + }) + t.Run("invalid nef", func(t *testing.T) { + require.NoError(t, os.WriteFile(badNef, []byte("qwer"), os.ModePerm)) + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", badNef) + }) + t.Run("invalid signers", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", goodNef, "--", "not-a-valid-signer") + }) + t.Run("no RPC endpoint", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://123456789", + "--in", goodNef) + }) + t.Run("good", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", goodNef) + }) + t.Run("good with hashed signer", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", goodNef, "--", util.Uint160{1, 2, 3}.StringLE()) + }) + t.Run("good with addressed signer", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--in", goodNef, "--", address.Uint160ToString(util.Uint160{1, 2, 3})) + }) + t.Run("historic, invalid", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--historic", "bad", + "--in", goodNef) + }) + t.Run("historic, index", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--historic", "0", + "--in", goodNef) + }) + t.Run("historic, hash", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--historic", e.Chain.GetHeaderHash(0).StringLE(), + "--in", goodNef) + }) +} + +func TestComlileAndInvokeFunction(t *testing.T) { + e := testcli.NewExecutor(t, true) + tmpDir := t.TempDir() + + nefName := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + tmp := t.TempDir() + configPath := filepath.Join(tmp, "config.yaml") + cfg := config.Wallet{ + Path: testcli.ValidatorWallet, + Password: testcli.ValidatorPass, + } + yml, err := yaml.Marshal(cfg) + require.NoError(t, err) + require.NoError(t, os.WriteFile(configPath, yml, 0666)) + e.Run(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--force", + "--wallet-config", configPath, "--address", testcli.ValidatorAddr, + "--in", nefName, "--manifest", manifestName) + + e.CheckTxPersisted(t, "Sent invocation transaction ") + line, err := e.Out.ReadString('\n') + require.NoError(t, err) + line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) + h, err := util.Uint160DecodeStringLE(line) + require.NoError(t, err) + + t.Run("check calc hash", func(t *testing.T) { + // missing sender + e.RunWithErrorCheck(t, `Required flag "sender" not set`, "neo-go", "contract", "calc-hash", + "--in", nefName, + "--manifest", manifestName) + + e.Run(t, "neo-go", "contract", "calc-hash", + "--sender", testcli.ValidatorAddr, "--in", nefName, + "--manifest", manifestName) + e.CheckNextLine(t, h.StringLE()) + }) + + cmd := []string{"neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} + t.Run("missing hash", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) + t.Run("invalid hash", func(t *testing.T) { + e.RunWithError(t, append(cmd, "notahash")...) + }) + + cmd = append(cmd, h.StringLE()) + t.Run("missing method", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) + + cmd = append(cmd, "getValue") + t.Run("invalid params", func(t *testing.T) { + e.RunWithError(t, append(cmd, "[")...) + }) + t.Run("invalid cosigner", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--", "notahash")...) + }) + t.Run("missing RPC address", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "contract", "testinvokefunction", + h.StringLE(), "getValue") + }) + + e.Run(t, cmd...) + + checkGetValueOut := func(str string) { + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, []byte(str), res.Stack[0].Value()) + } + checkGetValueOut("on create|sub create") + + // deploy verification contract + hVerify := deployVerifyContract(t, e) + + t.Run("real invoke", func(t *testing.T) { + cmd := []string{"neo-go", "contract", "invokefunction", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} + t.Run("missing wallet", func(t *testing.T) { + cmd := append(cmd, h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("non-existent wallet", func(t *testing.T) { + cmd := append(cmd, "--wallet", filepath.Join(tmpDir, "not.exists"), + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("corrupted wallet", func(t *testing.T) { + tmp := t.TempDir() + tmpPath := filepath.Join(tmp, "wallet.json") + require.NoError(t, os.WriteFile(tmpPath, []byte("{"), os.ModePerm)) + + cmd := append(cmd, "--wallet", tmpPath, + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("non-existent address", func(t *testing.T) { + cmd := append(cmd, "--wallet", testcli.ValidatorWallet, + "--address", random.Uint160().StringLE(), + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("invalid password", func(t *testing.T) { + e.In.WriteString("invalid_password\r") + cmd := append(cmd, "--wallet", testcli.ValidatorWallet, + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("good: default address", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("y\r") + e.Run(t, append(cmd, "--wallet", testcli.ValidatorWallet, h.StringLE(), "getValue")...) + }) + t.Run("good: from wallet config", func(t *testing.T) { + e.In.WriteString("y\r") + e.Run(t, append(cmd, "--wallet-config", configPath, h.StringLE(), "getValue")...) + }) + + cmd = append(cmd, "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr) + t.Run("cancelled", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("n\r") + e.RunWithError(t, append(cmd, h.StringLE(), "getValue")...) + }) + t.Run("confirmed", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("y\r") + e.Run(t, append(cmd, h.StringLE(), "getValue")...) + }) + + t.Run("failind method", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("y\r") + e.RunWithError(t, append(cmd, h.StringLE(), "fail")...) + + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--force", h.StringLE(), "fail")...) + }) + + t.Run("cosigner is deployed contract", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("y\r") + e.Run(t, append(cmd, h.StringLE(), "getValue", + "--", testcli.ValidatorAddr, hVerify.StringLE())...) + }) + + t.Run("with await", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--force", "--await", h.StringLE(), "getValue")...) + e.CheckAwaitableTxPersisted(t) + }) + }) + + t.Run("real invoke and save tx", func(t *testing.T) { + txout := filepath.Join(tmpDir, "test_contract_tx.json") + + cmd = []string{"neo-go", "contract", "invokefunction", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--out", txout, + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + } + + t.Run("without cosigner", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, hVerify.StringLE(), "verify")...) + }) + + t.Run("with cosigner", func(t *testing.T) { + t.Run("cosigner is sender (none)", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, append(cmd, h.StringLE(), "checkSenderWitness", "--", testcli.ValidatorAddr+":None")...) + }) + t.Run("cosigner is sender (customcontract)", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, h.StringLE(), "checkSenderWitness", "--", testcli.ValidatorAddr+":CustomContracts:"+h.StringLE())...) + }) + t.Run("cosigner is sender (global)", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, h.StringLE(), "checkSenderWitness", "--", testcli.ValidatorAddr+":Global")...) + }) + + acc, err := wallet.NewAccount() + require.NoError(t, err) + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + err = acc.ConvertMultisig(2, keys.PublicKeys{acc.PublicKey(), pk.PublicKey()}) + require.NoError(t, err) + + t.Run("cosigner is multisig account", func(t *testing.T) { + t.Run("missing in the wallet", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, append(cmd, hVerify.StringLE(), "verify", "--", acc.Address)...) + }) + + t.Run("good", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, hVerify.StringLE(), "verify", "--", testcli.MultisigAddr)...) + }) + }) + + t.Run("cosigner is deployed contract", func(t *testing.T) { + t.Run("missing in the wallet", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, append(cmd, hVerify.StringLE(), "verify", "--", h.StringLE())...) + }) + + t.Run("good", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, hVerify.StringLE(), "verify", "--", hVerify.StringLE())...) + }) + }) + }) + }) + + t.Run("test Storage.Find", func(t *testing.T) { + cmd := []string{"neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + h.StringLE(), "testFind"} + + t.Run("keys only", func(t *testing.T) { + e.Run(t, append(cmd, strconv.FormatInt(storage.FindKeysOnly, 10))...) + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State) + require.Len(t, res.Stack, 1) + require.Equal(t, []stackitem.Item{ + stackitem.Make("findkey1"), + stackitem.Make("findkey2"), + }, res.Stack[0].Value()) + }) + t.Run("both", func(t *testing.T) { + e.Run(t, append(cmd, strconv.FormatInt(storage.FindDefault, 10))...) + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State) + require.Len(t, res.Stack, 1) + + arr, ok := res.Stack[0].Value().([]stackitem.Item) + require.True(t, ok) + require.Len(t, arr, 2) + require.Equal(t, []stackitem.Item{ + stackitem.Make("findkey1"), stackitem.Make("value1"), + }, arr[0].Value()) + require.Equal(t, []stackitem.Item{ + stackitem.Make("findkey2"), stackitem.Make("value2"), + }, arr[1].Value()) + }) + }) + + var ( + hashBeforeUpdate util.Uint256 + indexBeforeUpdate uint32 + indexAfterUpdate uint32 + stateBeforeUpdate util.Uint256 + ) + t.Run("Update", func(t *testing.T) { + nefName := filepath.Join(tmpDir, "updated.nef") + manifestName := filepath.Join(tmpDir, "updated.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--config", "testdata/deploy/neo-go.yml", + "--in", "testdata/deploy/", // compile all files in dir + "--out", nefName, "--manifest", manifestName) + + t.Cleanup(func() { + os.Remove(nefName) + os.Remove(manifestName) + }) + + rawNef, err := os.ReadFile(nefName) + require.NoError(t, err) + rawManifest, err := os.ReadFile(manifestName) + require.NoError(t, err) + + indexBeforeUpdate = e.Chain.BlockHeight() + hashBeforeUpdate = e.Chain.CurrentHeaderHash() + mptBeforeUpdate, err := e.Chain.GetStateRoot(indexBeforeUpdate) + require.NoError(t, err) + stateBeforeUpdate = mptBeforeUpdate.Root + e.In.WriteString("one\r") + e.Run(t, "neo-go", "contract", "invokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, + "--force", + h.StringLE(), "update", + "bytes:"+hex.EncodeToString(rawNef), + "bytes:"+hex.EncodeToString(rawManifest), + "", + ) + e.CheckTxPersisted(t, "Sent invocation transaction ") + + indexAfterUpdate = e.Chain.BlockHeight() + e.In.WriteString("one\r") + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + h.StringLE(), "getValue") + checkGetValueOut("on update|sub update") + }) + t.Run("historic", func(t *testing.T) { + t.Run("bad ref", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--historic", "bad", + h.StringLE(), "getValue") + }) + for name, ref := range map[string]string{ + "by index": strconv.FormatUint(uint64(indexBeforeUpdate), 10), + "by block hash": hashBeforeUpdate.StringLE(), + "by state hash": stateBeforeUpdate.StringLE(), + } { + t.Run(name, func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--historic", ref, + h.StringLE(), "getValue") + }) + checkGetValueOut("on create|sub create") + } + t.Run("updated historic", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--historic", strconv.FormatUint(uint64(indexAfterUpdate), 10), + h.StringLE(), "getValue") + checkGetValueOut("on update|sub update") + }) + }) +} + +func TestContractInspect(t *testing.T) { + e := testcli.NewExecutor(t, false) + const srcPath = "testdata/deploy/main.go" + tmpDir := t.TempDir() + + nefName := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", srcPath, + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + cmd := []string{"neo-go", "contract", "inspect"} + t.Run("missing input", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "in" not set`, cmd...) + }) + t.Run("with raw '.go'", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--in", srcPath)...) + e.Run(t, append(cmd, "--in", srcPath, "--compile")...) + require.True(t, strings.Contains(e.Out.String(), "SYSCALL")) + }) + t.Run("with nef", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--in", nefName, "--compile")...) + e.RunWithError(t, append(cmd, "--in", filepath.Join(tmpDir, "not.exists"))...) + e.RunWithError(t, append(cmd, "--in", nefName, "something")...) + e.Run(t, append(cmd, "--in", nefName)...) + require.True(t, strings.Contains(e.Out.String(), "SYSCALL")) + }) +} + +func TestCompileExamples(t *testing.T) { + tmpDir := t.TempDir() + const examplePath = "../../examples" + infos, err := os.ReadDir(examplePath) + require.NoError(t, err) + + e := testcli.NewExecutor(t, false) + + for _, info := range infos { + if !info.IsDir() { + // example smart contracts are located in the `/examples` subdirectories, but + // there are also a couple of files inside the `/examples` which doesn't need to be compiled + continue + } + if info.Name() == "zkp" { + // A set of special ZKP-related examples, they have their own tests. + continue + } + t.Run(info.Name(), func(t *testing.T) { + infos, err := os.ReadDir(filepath.Join(examplePath, info.Name())) + require.NoError(t, err) + require.False(t, len(infos) == 0, "detected smart contract folder with no contract in it") + + outF := filepath.Join(tmpDir, info.Name()+".nef") + manifestF := filepath.Join(tmpDir, info.Name()+".manifest.json") + bindingF := filepath.Join(tmpDir, info.Name()+".binding.yml") + wrapperF := filepath.Join(tmpDir, info.Name()+".go") + rpcWrapperF := filepath.Join(tmpDir, info.Name()+".rpc.go") + + cfgName := filterFilename(infos, ".yml") + opts := []string{ + "neo-go", "contract", "compile", + "--in", filepath.Join(examplePath, info.Name()), + "--out", outF, + "--manifest", manifestF, + "--config", filepath.Join(examplePath, info.Name(), cfgName), + "--bindings", bindingF, + } + e.Run(t, opts...) + + if info.Name() == "storage" { + rawM, err := os.ReadFile(manifestF) + require.NoError(t, err) + + m := new(manifest.Manifest) + require.NoError(t, json.Unmarshal(rawM, m)) + + require.Nil(t, m.ABI.GetMethod("getDefault", 0)) + require.NotNil(t, m.ABI.GetMethod("get", 0)) + require.NotNil(t, m.ABI.GetMethod("get", 1)) + + require.Nil(t, m.ABI.GetMethod("putDefault", 1)) + require.NotNil(t, m.ABI.GetMethod("put", 1)) + require.NotNil(t, m.ABI.GetMethod("put", 2)) + } + e.Run(t, "neo-go", "contract", "generate-wrapper", + "--manifest", manifestF, + "--config", bindingF, + "--out", wrapperF, + "--hash", "0x00112233445566778899aabbccddeeff00112233") + e.Run(t, "neo-go", "contract", "generate-rpcwrapper", + "--manifest", manifestF, + "--config", bindingF, + "--out", rpcWrapperF, + "--hash", "0x00112233445566778899aabbccddeeff00112233") + }) + } + + t.Run("invalid manifest", func(t *testing.T) { + const dir = "./testdata/" + for _, name := range []string{"invalid1", "invalid2", "invalid3", "invalid4"} { + outF := filepath.Join(tmpDir, name+".nef") + manifestF := filepath.Join(tmpDir, name+".manifest.json") + e.RunWithError(t, "neo-go", "contract", "compile", + "--in", filepath.Join(dir, name), + "--out", outF, + "--manifest", manifestF, + "--config", filepath.Join(dir, name, "invalid.yml"), + ) + } + }) +} + +func filterFilename(infos []os.DirEntry, ext string) string { + for _, info := range infos { + if !info.IsDir() { + name := info.Name() + if strings.HasSuffix(name, ext) { + return name + } + } + } + return "" +} + +func TestContractCompile_NEFSizeCheck(t *testing.T) { + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + src := `package nefconstraints + var data = "%s" + + func Main() string { + return data + }` + data := make([]byte, stackitem.MaxSize-10) + for i := range data { + data[i] = byte('a') + } + + in := filepath.Join(tmpDir, "main.go") + cfg := filepath.Join(tmpDir, "main.yml") + require.NoError(t, os.WriteFile(cfg, []byte("name: main"), os.ModePerm)) + require.NoError(t, os.WriteFile(in, []byte(fmt.Sprintf(src, data)), os.ModePerm)) + + e.RunWithError(t, "neo-go", "contract", "compile", "--in", in) + require.NoFileExists(t, filepath.Join(tmpDir, "main.nef")) +} diff --git a/cli/smartcontract/generate.go b/cli/smartcontract/generate.go new file mode 100644 index 0000000..be79a08 --- /dev/null +++ b/cli/smartcontract/generate.go @@ -0,0 +1,124 @@ +package smartcontract + +import ( + "bytes" + "fmt" + "os" + "strings" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/rpcbinding" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/urfave/cli/v2" + "gopkg.in/yaml.v3" +) + +var generatorFlags = []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: `Configuration bindings file to use (*.yml). Configuration file is + generated by 'contract compile' command with --bindings flag`, + }, + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Read contract manifest (*.manifest.json) file", + Action: cmdargs.EnsureNotEmpty("manifest"), + }, + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, + Required: true, + Usage: "Output of the compiled wrapper", + Action: cmdargs.EnsureNotEmpty("out"), + }, + &cli.StringFlag{ + Name: "hash", + Usage: "Smart-contract hash. If not passed, the wrapper will be designed for dynamic hash usage", + }, +} + +var generateWrapperCmd = &cli.Command{ + Name: "generate-wrapper", + Usage: "Generate wrapper to use in other contracts", + UsageText: "neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]", + Description: `Generates a Go wrapper to use it in other smart contracts. If the + --hash flag is provided, CALLT instruction is used for the target contract + invocation as an optimization of the wrapper contract code. If omitted, the + generated wrapper will be designed for dynamic hash usage, allowing + the hash to be specified at runtime. +`, + Action: contractGenerateWrapper, + Flags: generatorFlags, +} + +var generateRPCWrapperCmd = &cli.Command{ + Name: "generate-rpcwrapper", + Usage: "Generate RPC wrapper to use for data reads", + UsageText: "neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]", + Action: contractGenerateRPCWrapper, + Flags: generatorFlags, +} + +func contractGenerateWrapper(ctx *cli.Context) error { + return contractGenerateSomething(ctx, binding.Generate) +} + +func contractGenerateRPCWrapper(ctx *cli.Context) error { + return contractGenerateSomething(ctx, rpcbinding.Generate) +} + +// contractGenerateSomething reads generator parameters and calls the given callback. +func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + var ( + h util.Uint160 + err error + ) + if hStr := ctx.String("hash"); len(hStr) != 0 { + h, err = util.Uint160DecodeStringLE(strings.TrimPrefix(hStr, "0x")) + if err != nil { + return cli.Exit(fmt.Errorf("invalid contract hash: %w", err), 1) + } + } + m, _, err := readManifest(ctx.String("manifest"), h) + if err != nil { + return cli.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1) + } + + cfg := binding.NewConfig() + if cfgPath := ctx.String("config"); cfgPath != "" { + bs, err := os.ReadFile(cfgPath) + if err != nil { + return cli.Exit(fmt.Errorf("can't read config file: %w", err), 1) + } + decoder := yaml.NewDecoder(bytes.NewReader(bs)) + decoder.KnownFields(true) + + err = decoder.Decode(&cfg) + if err != nil { + return cli.Exit(fmt.Errorf("can't parse config file: %w", err), 1) + } + } + cfg.Manifest = m + cfg.Hash = h + + f, err := os.Create(ctx.String("out")) + if err != nil { + return cli.Exit(fmt.Errorf("can't create output file: %w", err), 1) + } + defer f.Close() + + cfg.Output = f + + err = cb(cfg) + if err != nil { + return cli.Exit(fmt.Errorf("error during generation: %w", err), 1) + } + return nil +} diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go new file mode 100644 index 0000000..3824f75 --- /dev/null +++ b/cli/smartcontract/generate_test.go @@ -0,0 +1,701 @@ +package smartcontract_test + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestGenerate(t *testing.T) { + m := manifest.NewManifest("MyContract") + m.ABI.Methods = append(m.ABI.Methods, + manifest.Method{ + Name: manifest.MethodDeploy, + ReturnType: smartcontract.VoidType, + }, + manifest.Method{ + Name: "sum", + Parameters: []manifest.Parameter{ + manifest.NewParameter("first", smartcontract.IntegerType), + manifest.NewParameter("second", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + manifest.Method{ + Name: "sum", // overloaded method + Parameters: []manifest.Parameter{ + manifest.NewParameter("first", smartcontract.IntegerType), + manifest.NewParameter("second", smartcontract.IntegerType), + manifest.NewParameter("third", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + manifest.Method{ + Name: "sum3", + Parameters: []manifest.Parameter{}, + ReturnType: smartcontract.IntegerType, + Safe: true, + }, + manifest.Method{ + Name: "zum", + Parameters: []manifest.Parameter{ + manifest.NewParameter("type", smartcontract.IntegerType), + manifest.NewParameter("typev", smartcontract.IntegerType), + manifest.NewParameter("func", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + manifest.Method{ + Name: "justExecute", + Parameters: []manifest.Parameter{ + manifest.NewParameter("arr", smartcontract.ArrayType), + }, + ReturnType: smartcontract.VoidType, + }, + manifest.Method{ + Name: "getPublicKey", + Parameters: nil, + ReturnType: smartcontract.PublicKeyType, + }, + manifest.Method{ + Name: "otherTypes", + Parameters: []manifest.Parameter{ + manifest.NewParameter("ctr", smartcontract.Hash160Type), + manifest.NewParameter("tx", smartcontract.Hash256Type), + manifest.NewParameter("sig", smartcontract.SignatureType), + manifest.NewParameter("data", smartcontract.AnyType), + }, + ReturnType: smartcontract.BoolType, + }, + manifest.Method{ + Name: "searchStorage", + Parameters: []manifest.Parameter{ + manifest.NewParameter("ctx", smartcontract.InteropInterfaceType), + }, + ReturnType: smartcontract.InteropInterfaceType, + }, + manifest.Method{ + Name: "getFromMap", + Parameters: []manifest.Parameter{ + manifest.NewParameter("intMap", smartcontract.MapType), + manifest.NewParameter("indices", smartcontract.ArrayType), + }, + ReturnType: smartcontract.ArrayType, + }, + manifest.Method{ + Name: "doSomething", + Parameters: []manifest.Parameter{ + manifest.NewParameter("bytes", smartcontract.ByteArrayType), + manifest.NewParameter("str", smartcontract.StringType), + }, + ReturnType: smartcontract.InteropInterfaceType, + }, + manifest.Method{ + Name: "getBlockWrapper", + Parameters: []manifest.Parameter{}, + ReturnType: smartcontract.InteropInterfaceType, + }, + manifest.Method{ + Name: "myFunc", + Parameters: []manifest.Parameter{ + manifest.NewParameter("in", smartcontract.MapType), + }, + ReturnType: smartcontract.ArrayType, + }) + + manifestFile := filepath.Join(t.TempDir(), "manifest.json") + outFile := filepath.Join(t.TempDir(), "out.go") + + rawManifest, err := json.Marshal(m) + require.NoError(t, err) + require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) + + h := util.Uint160{ + 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, + 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, + } + e := testcli.NewExecutor(t, false) + + rawCfg := `package: wrapper +hash: ` + h.StringLE() + ` +overrides: + searchStorage.ctx: storage.Context + searchStorage: iterator.Iterator + getFromMap.intMap: "map[string]int" + getFromMap.indices: "[]string" + getFromMap: "[]int" + getBlockWrapper: ledger.Block + myFunc.in: "map[int]github.com/heyitsme/mycontract.Input" + myFunc: "[]github.com/heyitsme/mycontract.Output" +callflags: + doSomething: ReadStates +` + cfgPath := filepath.Join(t.TempDir(), "binding.yml") + require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) + + e.Run(t, []string{"", "contract", "generate-wrapper", + "--manifest", manifestFile, + "--config", cfgPath, + "--out", outFile, + "--hash", h.StringLE(), + }...) + + const expected = `// Code generated by neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package wrapper contains wrappers for MyContract contract. +package wrapper + +import ( + "github.com/heyitsme/mycontract" + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" + "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + "github.com/nspcc-dev/neo-go/pkg/interop/storage" +) + +// Hash contains contract hash in big-endian form. +const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04" + +// Sum invokes ` + "`sum`" + ` method of contract. +func Sum(first int, second int) int { + return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second).(int) +} + +// Sum2 invokes ` + "`sum`" + ` method of contract. +func Sum2(first int, second int, third int) int { + return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second, third).(int) +} + +// Sum3 invokes ` + "`sum3`" + ` method of contract. +func Sum3() int { + return neogointernal.CallWithToken(Hash, "sum3", int(contract.ReadOnly)).(int) +} + +// Zum invokes ` + "`zum`" + ` method of contract. +func Zum(typev int, typev_ int, funcv int) int { + return neogointernal.CallWithToken(Hash, "zum", int(contract.All), typev, typev_, funcv).(int) +} + +// JustExecute invokes ` + "`justExecute`" + ` method of contract. +func JustExecute(arr []any) { + neogointernal.CallWithTokenNoRet(Hash, "justExecute", int(contract.All), arr) +} + +// GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract. +func GetPublicKey() interop.PublicKey { + return neogointernal.CallWithToken(Hash, "getPublicKey", int(contract.All)).(interop.PublicKey) +} + +// OtherTypes invokes ` + "`otherTypes`" + ` method of contract. +func OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data any) bool { + return neogointernal.CallWithToken(Hash, "otherTypes", int(contract.All), ctr, tx, sig, data).(bool) +} + +// SearchStorage invokes ` + "`searchStorage`" + ` method of contract. +func SearchStorage(ctx storage.Context) iterator.Iterator { + return neogointernal.CallWithToken(Hash, "searchStorage", int(contract.All), ctx).(iterator.Iterator) +} + +// GetFromMap invokes ` + "`getFromMap`" + ` method of contract. +func GetFromMap(intMap map[string]int, indices []string) []int { + return neogointernal.CallWithToken(Hash, "getFromMap", int(contract.All), intMap, indices).([]int) +} + +// DoSomething invokes ` + "`doSomething`" + ` method of contract. +func DoSomething(bytes []byte, str string) any { + return neogointernal.CallWithToken(Hash, "doSomething", int(contract.ReadStates), bytes, str).(any) +} + +// GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract. +func GetBlockWrapper() ledger.Block { + return neogointernal.CallWithToken(Hash, "getBlockWrapper", int(contract.All)).(ledger.Block) +} + +// MyFunc invokes ` + "`myFunc`" + ` method of contract. +func MyFunc(in map[int]mycontract.Input) []mycontract.Output { + return neogointernal.CallWithToken(Hash, "myFunc", int(contract.All), in).([]mycontract.Output) +} +` + + data, err := os.ReadFile(outFile) + require.NoError(t, err) + require.Equal(t, expected, string(data)) + + e.Run(t, []string{"", "contract", "generate-wrapper", + "--manifest", manifestFile, + "--config", cfgPath, + "--out", outFile, + }...) + expectedWithDynamicHash := `// Code generated by neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package wrapper contains wrappers for MyContract contract. +package wrapper + +import ( + "github.com/heyitsme/mycontract" + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" + "github.com/nspcc-dev/neo-go/pkg/interop/storage" +) + +// Contract represents the MyContract smart contract. +type Contract struct { + Hash interop.Hash160 +} + +// NewContract returns a new Contract instance with the specified hash. +func NewContract(hash interop.Hash160) Contract { + return Contract{Hash: hash} +} + +// Sum invokes ` + "`sum`" + ` method of contract. +func (c Contract) Sum(first int, second int) int { + return contract.Call(c.Hash, "sum", contract.All, first, second).(int) +} + +// Sum2 invokes ` + "`sum`" + ` method of contract. +func (c Contract) Sum2(first int, second int, third int) int { + return contract.Call(c.Hash, "sum", contract.All, first, second, third).(int) +} + +// Sum3 invokes ` + "`sum3`" + ` method of contract. +func (c Contract) Sum3() int { + return contract.Call(c.Hash, "sum3", contract.ReadOnly).(int) +} + +// Zum invokes ` + "`zum`" + ` method of contract. +func (c Contract) Zum(typev int, typev_ int, funcv int) int { + return contract.Call(c.Hash, "zum", contract.All, typev, typev_, funcv).(int) +} + +// JustExecute invokes ` + "`justExecute`" + ` method of contract. +func (c Contract) JustExecute(arr []any) { + contract.Call(c.Hash, "justExecute", contract.All, arr) +} + +// GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract. +func (c Contract) GetPublicKey() interop.PublicKey { + return contract.Call(c.Hash, "getPublicKey", contract.All).(interop.PublicKey) +} + +// OtherTypes invokes ` + "`otherTypes`" + ` method of contract. +func (c Contract) OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data any) bool { + return contract.Call(c.Hash, "otherTypes", contract.All, ctr, tx, sig, data).(bool) +} + +// SearchStorage invokes ` + "`searchStorage`" + ` method of contract. +func (c Contract) SearchStorage(ctx storage.Context) iterator.Iterator { + return contract.Call(c.Hash, "searchStorage", contract.All, ctx).(iterator.Iterator) +} + +// GetFromMap invokes ` + "`getFromMap`" + ` method of contract. +func (c Contract) GetFromMap(intMap map[string]int, indices []string) []int { + return contract.Call(c.Hash, "getFromMap", contract.All, intMap, indices).([]int) +} + +// DoSomething invokes ` + "`doSomething`" + ` method of contract. +func (c Contract) DoSomething(bytes []byte, str string) any { + return contract.Call(c.Hash, "doSomething", contract.ReadStates, bytes, str).(any) +} + +// GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract. +func (c Contract) GetBlockWrapper() ledger.Block { + return contract.Call(c.Hash, "getBlockWrapper", contract.All).(ledger.Block) +} + +// MyFunc invokes ` + "`myFunc`" + ` method of contract. +func (c Contract) MyFunc(in map[int]mycontract.Input) []mycontract.Output { + return contract.Call(c.Hash, "myFunc", contract.All, in).([]mycontract.Output) +} +` + data, err = os.ReadFile(outFile) + require.NoError(t, err) + require.Equal(t, expectedWithDynamicHash, string(data)) +} + +func TestGenerateValidPackageName(t *testing.T) { + m := manifest.NewManifest("My space\tcontract") + m.ABI.Methods = append(m.ABI.Methods, + manifest.Method{ + Name: "get", + Parameters: []manifest.Parameter{}, + ReturnType: smartcontract.IntegerType, + Safe: true, + }, + ) + + manifestFile := filepath.Join(t.TempDir(), "manifest.json") + outFile := filepath.Join(t.TempDir(), "out.go") + + rawManifest, err := json.Marshal(m) + require.NoError(t, err) + require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) + + h := util.Uint160{ + 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, + 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, + } + e := testcli.NewExecutor(t, false) + e.Run(t, []string{"", "contract", "generate-wrapper", + "--manifest", manifestFile, + "--out", outFile, + "--hash", "0x" + h.StringLE(), + }...) + + data, err := os.ReadFile(outFile) + require.NoError(t, err) + require.Equal(t, `// Code generated by neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package myspacecontract contains wrappers for My space contract contract. +package myspacecontract + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" +) + +// Hash contains contract hash in big-endian form. +const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04" + +// Get invokes `+"`get`"+` method of contract. +func Get() int { + return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int) +} +`, string(data)) + e.Run(t, []string{"", "contract", "generate-rpcwrapper", + "--manifest", manifestFile, + "--out", outFile, + "--hash", "0x" + h.StringLE(), + }...) + + data, err = os.ReadFile(outFile) + require.NoError(t, err) + require.Equal(t, `// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package myspacecontract contains RPC wrappers for My space contract contract. +package myspacecontract + +import ( + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" + "math/big" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x4, 0x8, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x0, 0x1, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad, 0xbe, 0xef, 0x3, 0x4} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{invoker, hash} +} + +// Get invokes `+"`get`"+` method of contract. +func (c *ContractReader) Get() (*big.Int, error) { + return unwrap.BigInt(c.invoker.Call(c.hash, "get")) +} +`, string(data)) +} + +// rewriteExpectedOutputs denotes whether expected output files should be rewritten +// for TestGenerateRPCBindings and TestAssistedRPCBindings. +const rewriteExpectedOutputs = false + +func TestGenerateRPCBindings(t *testing.T) { + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + var checkBinding = func(manifest string, hash string, good string) { + t.Run(manifest, func(t *testing.T) { + outFile := filepath.Join(tmpDir, "out.go") + e.Run(t, []string{"", "contract", "generate-rpcwrapper", + "--manifest", manifest, + "--out", outFile, + "--hash", hash, + }...) + + data, err := os.ReadFile(outFile) + require.NoError(t, err) + data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. + if rewriteExpectedOutputs { + require.NoError(t, os.WriteFile(good, data, os.ModePerm)) + } else { + expected, err := os.ReadFile(good) + require.NoError(t, err) + expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. + require.Equal(t, string(expected), string(data)) + } + }) + } + + checkBinding(filepath.Join("testdata", "nex", "nex.manifest.json"), + "0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8", + filepath.Join("testdata", "nex", "nex.go")) + checkBinding(filepath.Join("testdata", "nameservice", "nns.manifest.json"), + "0x50ac1c37690cc2cfc594472833cf57505d5f46de", + filepath.Join("testdata", "nameservice", "nns.go")) + checkBinding(filepath.Join("testdata", "gas", "gas.manifest.json"), + "0xd2a4cff31913016155e38e474a2c06d08be276cf", + filepath.Join("testdata", "gas", "gas.go")) + checkBinding(filepath.Join("testdata", "verifyrpc", "verify.manifest.json"), + "0x00112233445566778899aabbccddeeff00112233", + filepath.Join("rpcbindings", "verify", "rpcbindings_test.go")) + checkBinding(filepath.Join("testdata", "nonepiter", "iter.manifest.json"), + "0x00112233445566778899aabbccddeeff00112233", + filepath.Join("testdata", "nonepiter", "iter.go")) + + require.False(t, rewriteExpectedOutputs) +} + +func TestAssistedRPCBindings(t *testing.T) { + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + var checkBinding = func(source, configFile, expectedFile string, hasDefinedHash, guessEventTypes bool, suffix ...string) { + testName := source + if len(suffix) != 0 { + testName += suffix[0] + } + testName += fmt.Sprintf(", predefined hash: %t", hasDefinedHash) + t.Run(testName, func(t *testing.T) { + outFile := filepath.Join(tmpDir, "out.go") + if configFile == "" { + if len(suffix) != 0 { + configFile = filepath.Join(source, "config_"+suffix[0]+".yml") + } else { + configFile = filepath.Join(source, "config.yml") + } + } + if expectedFile == "" { + // Expected bindings are moved out of testdata to be able to perform static code analysis on them. + expectedRoot := strings.Replace(source, "testdata", ".", 1) + expectedFile = filepath.Join(expectedRoot, "rpcbindings", "rpcbindings_test.go") + if len(suffix) != 0 { + expectedFile = filepath.Join(expectedRoot, "rpcbindings", suffix[0], "rpcbindings_test.go") + } else if !hasDefinedHash { + expectedFile = filepath.Join(expectedRoot, "rpcbindings", "dynamic_hash", "rpcbindings_test.go") + } + } + manifestF := filepath.Join(tmpDir, "manifest.json") + bindingF := filepath.Join(tmpDir, "binding.yml") + nefF := filepath.Join(tmpDir, "out.nef") + cmd := []string{"", "contract", "compile", + "--in", source, + "--config", configFile, + "--manifest", manifestF, + "--bindings", bindingF, + "--out", nefF, + } + if guessEventTypes { + cmd = append(cmd, "--guess-eventtypes") + } + e.Run(t, cmd...) + + cmds := []string{"", "contract", "generate-rpcwrapper", + "--config", bindingF, + "--manifest", manifestF, + "--out", outFile, + } + if hasDefinedHash { + cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233") + } + e.Run(t, cmds...) + + data, err := os.ReadFile(outFile) + require.NoError(t, err) + data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. + if rewriteExpectedOutputs { + require.NoError(t, os.WriteFile(expectedFile, data, os.ModePerm)) + } else { + expected, err := os.ReadFile(expectedFile) + require.NoError(t, err) + expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. + require.Equal(t, string(expected), string(data)) + } + }) + } + + for _, hasDefinedHash := range []bool{true, false} { + checkBinding(filepath.Join("testdata", "rpcbindings", "types"), "", "", hasDefinedHash, false) + checkBinding(filepath.Join("testdata", "rpcbindings", "structs"), "", "", hasDefinedHash, false) + checkBinding(filepath.Join("testdata", "rpcbindings", "royalty"), "", "", hasDefinedHash, false) + } + checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), "", "", true, false) + checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), "", "", true, false, "extended") + checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), "", "", true, true, "guessed") + + checkBinding(filepath.Join("..", "..", "examples", "nft-d"), filepath.Join("..", "..", "examples", "nft-d", "nft.yml"), filepath.Join("rpcbindings", "nft-d", "dynamic_hash", "rpcbindings_test.go"), false, false) + checkBinding(filepath.Join("..", "..", "examples", "nft-d"), filepath.Join("..", "..", "examples", "nft-d", "nft.yml"), filepath.Join("rpcbindings", "nft-d", "rpcbindings_test.go"), true, true) + checkBinding(filepath.Join("..", "..", "examples", "nft-nd"), filepath.Join("..", "..", "examples", "nft-nd", "nft.yml"), filepath.Join("rpcbindings", "nft-nd", "dynamic_hash", "rpcbindings_test.go"), false, false) + checkBinding(filepath.Join("..", "..", "examples", "nft-nd"), filepath.Join("..", "..", "examples", "nft-nd", "nft.yml"), filepath.Join("rpcbindings", "nft-nd", "rpcbindings_test.go"), true, true) + + require.False(t, rewriteExpectedOutputs) +} + +func TestGenerate_Errors(t *testing.T) { + e := testcli.NewExecutor(t, false) + args := []string{"neo-go", "contract", "generate-wrapper"} + + t.Run("invalid hash", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "invalid contract hash", append(args, "--hash", "xxx", "--manifest", "yyy", "--out", "zzz")...) + }) + t.Run("missing manifest argument", func(t *testing.T) { + e.RunWithErrorCheck(t, `Required flag "manifest" not set`, append(args, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) + }) + t.Run("missing manifest file", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "can't read contract manifest", append(args, "--manifest", "notexists", "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) + }) + t.Run("empty manifest", func(t *testing.T) { + manifestFile := filepath.Join(t.TempDir(), "invalid.json") + require.NoError(t, os.WriteFile(manifestFile, []byte("[]"), os.ModePerm)) + e.RunWithErrorCheckExit(t, "json: cannot unmarshal array into Go value of type manifest.Manifest", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) + }) + t.Run("invalid manifest", func(t *testing.T) { + manifestFile := filepath.Join(t.TempDir(), "invalid.json") + m := manifest.NewManifest("MyContract") // no methods + rawManifest, err := json.Marshal(m) + require.NoError(t, err) + require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) + e.RunWithErrorCheckExit(t, "ABI: no methods", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) + }) + + manifestFile := filepath.Join(t.TempDir(), "manifest.json") + m := manifest.NewManifest("MyContract") + m.ABI.Methods = append(m.ABI.Methods, manifest.Method{ + Name: "method0", + Offset: 0, + ReturnType: smartcontract.AnyType, + Safe: true, + }) + rawManifest, err := json.Marshal(m) + require.NoError(t, err) + require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) + + t.Run("missing config", func(t *testing.T) { + e.RunWithErrorCheckExit(t, "can't read config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), + "--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz")...) + }) + t.Run("invalid config", func(t *testing.T) { + rawCfg := `package: wrapper +callflags: + someFunc: ReadSometimes +` + cfgPath := filepath.Join(t.TempDir(), "binding.yml") + require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) + + e.RunWithErrorCheckExit(t, "can't parse config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), + "--config", cfgPath, "--out", "zzz")...) + }) +} + +func TestCompile_GuessEventTypes(t *testing.T) { + e := testcli.NewExecutor(t, false) + check := func(t *testing.T, source string, expectedErrText string) { + tmpDir := t.TempDir() + configFile := filepath.Join(source, "invalid.yml") + manifestF := filepath.Join(tmpDir, "invalid.manifest.json") + bindingF := filepath.Join(tmpDir, "invalid.binding.yml") + nefF := filepath.Join(tmpDir, "invalid.out.nef") + cmd := []string{"", "contract", "compile", + "--in", source, + "--config", configFile, + "--manifest", manifestF, + "--bindings", bindingF, + "--out", nefF, + "--guess-eventtypes", + } + e.RunWithErrorCheckExit(t, expectedErrText, cmd...) + } + + t.Run("not declared in manifest", func(t *testing.T) { + check(t, filepath.Join("testdata", "rpcbindings", "invalid1"), "inconsistent usages of event `Non declared event`: not declared in the contract config") + }) + t.Run("invalid number of params", func(t *testing.T) { + check(t, filepath.Join("testdata", "rpcbindings", "invalid2"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1") + }) + /* + // TODO: this on is a controversial one. If event information is provided in the config file, then conversion code + // will be emitted by the compiler according to the parameter type provided via config. Thus, we can be sure that + // either event parameter has the type specified in the config file or the execution of the contract will fail. + // Thus, this testcase is always failing (no compilation error occurs). + // Question: do we want to compare `RealType` of the emitted parameter with the one expected in the manifest? + t.Run("SC parameter type mismatch", func(t *testing.T) { + check(t, filepath.Join("testdata", "rpcbindings", "invalid3"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1") + }) + */ + t.Run("extended types mismatch", func(t *testing.T) { + check(t, filepath.Join("testdata", "rpcbindings", "invalid4"), "inconsistent usages of event `SomeEvent`: extended type of param #0 mismatch") + }) + t.Run("named types redeclare", func(t *testing.T) { + check(t, filepath.Join("testdata", "rpcbindings", "invalid5"), "configured declared named type intersects with the contract's one: `invalid5.NamedStruct`") + }) +} + +func TestGenerateRPCBindings_Errors(t *testing.T) { + e := testcli.NewExecutor(t, false) + t.Run("duplicating resulting fields", func(t *testing.T) { + check := func(t *testing.T, packageName string, autogen bool, expectedError string) { + tmpDir := t.TempDir() + source := filepath.Join("testdata", "rpcbindings", packageName) + configFile := filepath.Join(source, "invalid.yml") + out := filepath.Join(tmpDir, "rpcbindings.out") + manifestF := filepath.Join(tmpDir, "manifest.json") + bindingF := filepath.Join(tmpDir, "binding.yml") + nefF := filepath.Join(tmpDir, "out.nef") + cmd := []string{"", "contract", "compile", + "--in", source, + "--config", configFile, + "--manifest", manifestF, + "--bindings", bindingF, + "--out", nefF, + } + if autogen { + cmd = append(cmd, "--guess-eventtypes") + } + e.Run(t, cmd...) + + cmds := []string{"", "contract", "generate-rpcwrapper", + "--config", bindingF, + "--manifest", manifestF, + "--out", out, + } + e.RunWithErrorCheckExit(t, expectedError, cmds...) + } + + t.Run("event", func(t *testing.T) { + check(t, "invalid6", false, "error during generation: named type `SomeStruct` has two fields with identical resulting binding name `Field`") + }) + t.Run("autogen event", func(t *testing.T) { + check(t, "invalid7", true, "error during generation: named type `invalid7.SomeStruct` has two fields with identical resulting binding name `Field`") + }) + t.Run("struct", func(t *testing.T) { + check(t, "invalid8", false, "error during generation: named type `invalid8.SomeStruct` has two fields with identical resulting binding name `Field`") + }) + }) +} diff --git a/cli/smartcontract/manifest.go b/cli/smartcontract/manifest.go new file mode 100644 index 0000000..8d65fcc --- /dev/null +++ b/cli/smartcontract/manifest.go @@ -0,0 +1,104 @@ +package smartcontract + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/urfave/cli/v2" +) + +func manifestAddGroup(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + sender := ctx.Generic("sender").(*flags.Address) + nf, _, err := readNEFFile(ctx.String("nef")) + if err != nil { + return cli.Exit(fmt.Errorf("can't read NEF file: %w", err), 1) + } + + mPath := ctx.String("manifest") + m, _, err := readManifest(mPath, util.Uint160{}) + if err != nil { + return cli.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1) + } + + h := state.CreateContractHash(sender.Uint160(), nf.Checksum, m.Name) + + gAcc, w, err := options.GetAccFromContext(ctx) + if err != nil { + return cli.Exit(fmt.Errorf("can't get account to sign group with: %w", err), 1) + } + defer w.Close() + + var found bool + + sig := gAcc.PrivateKey().Sign(h.BytesBE()) + pub := gAcc.PublicKey() + for i := range m.Groups { + if m.Groups[i].PublicKey.Equal(pub) { + m.Groups[i].Signature = sig + found = true + break + } + } + if !found { + m.Groups = append(m.Groups, manifest.Group{ + PublicKey: pub, + Signature: sig, + }) + } + + rawM, err := json.Marshal(m) + if err != nil { + return cli.Exit(fmt.Errorf("can't marshal manifest: %w", err), 1) + } + + err = os.WriteFile(mPath, rawM, os.ModePerm) + if err != nil { + return cli.Exit(fmt.Errorf("can't write manifest file: %w", err), 1) + } + return nil +} + +func readNEFFile(filename string) (*nef.File, []byte, error) { + f, err := os.ReadFile(filename) + if err != nil { + return nil, nil, err + } + + nefFile, err := nef.FileFromBytes(f) + if err != nil { + return nil, nil, fmt.Errorf("can't parse NEF file: %w", err) + } + + return &nefFile, f, nil +} + +// readManifest unmarshalls manifest got from the provided filename and checks +// it for validness against the provided contract hash. If empty hash is specified +// then no hash-related manifest groups check is performed. +func readManifest(filename string, hash util.Uint160) (*manifest.Manifest, []byte, error) { + manifestBytes, err := os.ReadFile(filename) + if err != nil { + return nil, nil, err + } + + m := new(manifest.Manifest) + err = json.Unmarshal(manifestBytes, m) + if err != nil { + return nil, nil, err + } + if err := m.IsValid(hash, true); err != nil { + return nil, nil, fmt.Errorf("manifest is invalid: %w", err) + } + return m, manifestBytes, nil +} diff --git a/cli/smartcontract/permission.go b/cli/smartcontract/permission.go new file mode 100644 index 0000000..9f10edf --- /dev/null +++ b/cli/smartcontract/permission.go @@ -0,0 +1,130 @@ +package smartcontract + +import ( + "errors" + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/util" + "gopkg.in/yaml.v3" +) + +type permission manifest.Permission + +const ( + permHashKey = "hash" + permGroupKey = "group" + permMethodKey = "methods" +) + +func (p permission) MarshalYAML() (any, error) { + m := yaml.Node{Kind: yaml.MappingNode} + switch p.Contract.Type { + case manifest.PermissionWildcard: + case manifest.PermissionHash: + m.Content = append(m.Content, + &yaml.Node{Kind: yaml.ScalarNode, Value: permHashKey}, + &yaml.Node{Kind: yaml.ScalarNode, Value: p.Contract.Value.(util.Uint160).StringLE()}) + case manifest.PermissionGroup: + m.Content = append(m.Content, + &yaml.Node{Kind: yaml.ScalarNode, Value: permGroupKey}, + &yaml.Node{Kind: yaml.ScalarNode, Value: p.Contract.Value.(*keys.PublicKey).StringCompressed()}) + default: + return nil, fmt.Errorf("invalid permission type: %d", p.Contract.Type) + } + + var val any = "*" + if !p.Methods.IsWildcard() { + val = p.Methods.Value + } + + n := &yaml.Node{Kind: yaml.ScalarNode} + err := n.Encode(val) + if err != nil { + return nil, err + } + m.Content = append(m.Content, + &yaml.Node{Kind: yaml.ScalarNode, Value: permMethodKey}, + n) + + return m, nil +} + +func (p *permission) UnmarshalYAML(node *yaml.Node) error { + var m map[string]any + if err := node.Decode(&m); err != nil { + return err + } + + if err := p.fillType(m); err != nil { + return err + } + + return p.fillMethods(m) +} + +func (p *permission) fillType(m map[string]any) error { + vh, ok1 := m[permHashKey] + vg, ok2 := m[permGroupKey] + switch { + case ok1 && ok2: + return errors.New("permission must have either 'hash' or 'group' field") + case ok1: + s, ok := vh.(string) + if !ok { + return errors.New("invalid 'hash' type") + } + + u, err := util.Uint160DecodeStringLE(s) + if err != nil { + return err + } + + p.Contract.Type = manifest.PermissionHash + p.Contract.Value = u + case ok2: + s, ok := vg.(string) + if !ok { + return errors.New("invalid 'hash' type") + } + + pub, err := keys.NewPublicKeyFromString(s) + if err != nil { + return err + } + + p.Contract.Type = manifest.PermissionGroup + p.Contract.Value = pub + default: + p.Contract.Type = manifest.PermissionWildcard + } + return nil +} + +func (p *permission) fillMethods(m map[string]any) error { + methods, ok := m[permMethodKey] + if !ok { + return errors.New("'methods' field is missing from permission") + } + + switch mt := methods.(type) { + case string: + if mt == "*" { + p.Methods.Value = nil + return nil + } + case []any: + ms := make([]string, len(mt)) + for i := range mt { + ms[i], ok = mt[i].(string) + if !ok { + return errors.New("invalid permission method name") + } + } + p.Methods.Value = ms + return nil + default: + } + return errors.New("'methods' field is invalid") +} diff --git a/cli/smartcontract/rpcbindings/nft-d/dynamic_hash/rpcbindings_test.go b/cli/smartcontract/rpcbindings/nft-d/dynamic_hash/rpcbindings_test.go new file mode 100644 index 0000000..01b8de7 --- /dev/null +++ b/cli/smartcontract/rpcbindings/nft-d/dynamic_hash/rpcbindings_test.go @@ -0,0 +1,60 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nft contains RPC wrappers for NeoFS Object NFT contract. +package nft + +import ( + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep22" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// NEP22Contract is an alias for nep22.Contract. +type NEP22Contract = nep22.Contract + +// NEP31Contract is an alias for nep31.Contract. +type NEP31Contract = nep31.Contract + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep11.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep11.Actor +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep11.DivisibleReader + nep24.RoyaltyReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep11.DivisibleWriter + NEP22Contract + NEP31Contract + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker. +func NewReader(invoker Invoker, hash util.Uint160) *ContractReader { + return &ContractReader{*nep11.NewDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using provided contract hash and the given Actor. +func New(actor Actor, hash util.Uint160) *Contract { + var nep11dt = nep11.NewDivisible(actor, hash) + var nep24t = nep24.NewRoyaltyReader(actor, hash) + return &Contract{ContractReader{nep11dt.DivisibleReader, *nep24t, actor, hash}, nep11dt.DivisibleWriter, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash} +} diff --git a/cli/smartcontract/rpcbindings/nft-d/rpcbindings_test.go b/cli/smartcontract/rpcbindings/nft-d/rpcbindings_test.go new file mode 100644 index 0000000..d6bac79 --- /dev/null +++ b/cli/smartcontract/rpcbindings/nft-d/rpcbindings_test.go @@ -0,0 +1,65 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nft contains RPC wrappers for NeoFS Object NFT contract. +package nft + +import ( + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep22" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// NEP22Contract is an alias for nep22.Contract. +type NEP22Contract = nep22.Contract + +// NEP31Contract is an alias for nep31.Contract. +type NEP31Contract = nep31.Contract + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep11.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep11.Actor +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep11.DivisibleReader + nep24.RoyaltyReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep11.DivisibleWriter + NEP22Contract + NEP31Contract + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{*nep11.NewDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + var nep11dt = nep11.NewDivisible(actor, hash) + var nep24t = nep24.NewRoyaltyReader(actor, hash) + return &Contract{ContractReader{nep11dt.DivisibleReader, *nep24t, actor, hash}, nep11dt.DivisibleWriter, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash} +} diff --git a/cli/smartcontract/rpcbindings/nft-nd/dynamic_hash/rpcbindings_test.go b/cli/smartcontract/rpcbindings/nft-nd/dynamic_hash/rpcbindings_test.go new file mode 100644 index 0000000..d5bebf5 --- /dev/null +++ b/cli/smartcontract/rpcbindings/nft-nd/dynamic_hash/rpcbindings_test.go @@ -0,0 +1,234 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nft contains RPC wrappers for HASHY NFT contract. +package nft + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep22" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" +) + +// NEP22Contract is an alias for nep22.Contract. +type NEP22Contract = nep22.Contract + +// NEP31Contract is an alias for nep31.Contract. +type NEP31Contract = nep31.Contract + +// NftRoyaltyRecipientShare is a contract-specific nft.RoyaltyRecipientShare type used by its methods. +type NftRoyaltyRecipientShare struct { + Address util.Uint160 + Share *big.Int +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep11.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep11.Actor + + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep11.NonDivisibleReader + nep24.RoyaltyReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep11.BaseWriter + NEP22Contract + NEP31Contract + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker. +func NewReader(invoker Invoker, hash util.Uint160) *ContractReader { + return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using provided contract hash and the given Actor. +func New(actor Actor, hash util.Uint160) *Contract { + var nep11ndt = nep11.NewNonDivisible(actor, hash) + var nep24t = nep24.NewRoyaltyReader(actor, hash) + return &Contract{ContractReader{nep11ndt.NonDivisibleReader, *nep24t, actor, hash}, nep11ndt.BaseWriter, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash} +} + +func (c *Contract) scriptForSetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(c.hash, "setRoyaltyInfo", ctx, tokenID, recipients) +} + +// SetRoyaltyInfo creates a transaction invoking `setRoyaltyInfo` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) SetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (util.Uint256, uint32, error) { + script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients) + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script) +} + +// SetRoyaltyInfoTransaction creates a transaction invoking `setRoyaltyInfo` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) SetRoyaltyInfoTransaction(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) { + script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) +} + +// SetRoyaltyInfoUnsigned creates a transaction invoking `setRoyaltyInfo` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) SetRoyaltyInfoUnsigned(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) { + script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients) + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil) +} + +// itemToNftRoyaltyRecipientShare converts stack item into *NftRoyaltyRecipientShare. +// NULL item is returned as nil pointer without error. +func itemToNftRoyaltyRecipientShare(item stackitem.Item, err error) (*NftRoyaltyRecipientShare, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(NftRoyaltyRecipientShare) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *NftRoyaltyRecipientShare is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&NftRoyaltyRecipientShare{}) + +// Ensure *NftRoyaltyRecipientShare is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&NftRoyaltyRecipientShare{}) + +// FromStackItem retrieves fields of NftRoyaltyRecipientShare from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *NftRoyaltyRecipientShare) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Address, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Address: %w", err) + } + + index++ + res.Share, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Share: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing NftRoyaltyRecipientShare. +// It implements [stackitem.Convertible] interface. +func (res *NftRoyaltyRecipientShare) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray(res.Address.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Address: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Share), error(nil) + if err != nil { + return nil, fmt.Errorf("field Share: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing NftRoyaltyRecipientShare. +// It implements [smartcontract.Convertible] interface so that NftRoyaltyRecipientShare +// could be used with invokers. +func (res *NftRoyaltyRecipientShare) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Address) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Address: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Share) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Share: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} diff --git a/cli/smartcontract/rpcbindings/nft-nd/rpcbindings_test.go b/cli/smartcontract/rpcbindings/nft-nd/rpcbindings_test.go new file mode 100644 index 0000000..a22211c --- /dev/null +++ b/cli/smartcontract/rpcbindings/nft-nd/rpcbindings_test.go @@ -0,0 +1,239 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nft contains RPC wrappers for HASHY NFT contract. +package nft + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep22" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// NEP22Contract is an alias for nep22.Contract. +type NEP22Contract = nep22.Contract + +// NEP31Contract is an alias for nep31.Contract. +type NEP31Contract = nep31.Contract + +// NftRoyaltyRecipientShare is a contract-specific nft.RoyaltyRecipientShare type used by its methods. +type NftRoyaltyRecipientShare struct { + Address util.Uint160 + Share *big.Int +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep11.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep11.Actor + + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep11.NonDivisibleReader + nep24.RoyaltyReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep11.BaseWriter + NEP22Contract + NEP31Contract + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + var nep11ndt = nep11.NewNonDivisible(actor, hash) + var nep24t = nep24.NewRoyaltyReader(actor, hash) + return &Contract{ContractReader{nep11ndt.NonDivisibleReader, *nep24t, actor, hash}, nep11ndt.BaseWriter, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash} +} + +func (c *Contract) scriptForSetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(c.hash, "setRoyaltyInfo", ctx, tokenID, recipients) +} + +// SetRoyaltyInfo creates a transaction invoking `setRoyaltyInfo` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) SetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (util.Uint256, uint32, error) { + script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients) + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script) +} + +// SetRoyaltyInfoTransaction creates a transaction invoking `setRoyaltyInfo` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) SetRoyaltyInfoTransaction(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) { + script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) +} + +// SetRoyaltyInfoUnsigned creates a transaction invoking `setRoyaltyInfo` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) SetRoyaltyInfoUnsigned(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) { + script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients) + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil) +} + +// itemToNftRoyaltyRecipientShare converts stack item into *NftRoyaltyRecipientShare. +// NULL item is returned as nil pointer without error. +func itemToNftRoyaltyRecipientShare(item stackitem.Item, err error) (*NftRoyaltyRecipientShare, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(NftRoyaltyRecipientShare) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *NftRoyaltyRecipientShare is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&NftRoyaltyRecipientShare{}) + +// Ensure *NftRoyaltyRecipientShare is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&NftRoyaltyRecipientShare{}) + +// FromStackItem retrieves fields of NftRoyaltyRecipientShare from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *NftRoyaltyRecipientShare) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Address, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Address: %w", err) + } + + index++ + res.Share, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Share: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing NftRoyaltyRecipientShare. +// It implements [stackitem.Convertible] interface. +func (res *NftRoyaltyRecipientShare) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray(res.Address.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Address: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Share), error(nil) + if err != nil { + return nil, fmt.Errorf("field Share: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing NftRoyaltyRecipientShare. +// It implements [smartcontract.Convertible] interface so that NftRoyaltyRecipientShare +// could be used with invokers. +func (res *NftRoyaltyRecipientShare) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Address) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Address: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Share) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Share: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} diff --git a/cli/smartcontract/rpcbindings/notifications/rpcbindings/extended/rpcbindings_test.go b/cli/smartcontract/rpcbindings/notifications/rpcbindings/extended/rpcbindings_test.go new file mode 100755 index 0000000..c0b255a --- /dev/null +++ b/cli/smartcontract/rpcbindings/notifications/rpcbindings/extended/rpcbindings_test.go @@ -0,0 +1,746 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package structs contains RPC wrappers for Notifications contract. +package structs + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// CrazyStruct is a contract-specific crazyStruct type used by its methods. +type CrazyStruct struct { + I *big.Int + B bool +} + +// SimpleStruct is a contract-specific simpleStruct type used by its methods. +type SimpleStruct struct { + I *big.Int +} + +// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract. +type ComplicatedNameEvent struct { + ComplicatedParam string +} + +// SomeMapEvent represents "SomeMap" event emitted by the contract. +type SomeMapEvent struct { + M map[*big.Int]map[string][]util.Uint160 +} + +// SomeStructEvent represents "SomeStruct" event emitted by the contract. +type SomeStructEvent struct { + S *CrazyStruct +} + +// SomeArrayEvent represents "SomeArray" event emitted by the contract. +type SomeArrayEvent struct { + A [][]*big.Int +} + +// SomeUnexportedFieldEvent represents "SomeUnexportedField" event emitted by the contract. +type SomeUnexportedFieldEvent struct { + S *SimpleStruct +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor + hash util.Uint160 +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + return &Contract{actor, hash} +} + +// Array creates a transaction invoking `array` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Array() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "array") +} + +// ArrayTransaction creates a transaction invoking `array` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "array") +} + +// ArrayUnsigned creates a transaction invoking `array` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) ArrayUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "array", nil) +} + +// CrazyMap creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) CrazyMap() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "crazyMap") +} + +// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "crazyMap") +} + +// CrazyMapUnsigned creates a transaction invoking `crazyMap` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) CrazyMapUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "crazyMap", nil) +} + +// Main creates a transaction invoking `main` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Main() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "main") +} + +// MainTransaction creates a transaction invoking `main` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MainTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "main") +} + +// MainUnsigned creates a transaction invoking `main` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) MainUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "main", nil) +} + +// Struct creates a transaction invoking `struct` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Struct() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "struct") +} + +// StructTransaction creates a transaction invoking `struct` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) StructTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "struct") +} + +// StructUnsigned creates a transaction invoking `struct` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) StructUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "struct", nil) +} + +// UnexportedField creates a transaction invoking `unexportedField` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) UnexportedField() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "unexportedField") +} + +// UnexportedFieldTransaction creates a transaction invoking `unexportedField` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UnexportedFieldTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "unexportedField") +} + +// UnexportedFieldUnsigned creates a transaction invoking `unexportedField` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) UnexportedFieldUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "unexportedField", nil) +} + +// itemToCrazyStruct converts stack item into *CrazyStruct. +// NULL item is returned as nil pointer without error. +func itemToCrazyStruct(item stackitem.Item, err error) (*CrazyStruct, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(CrazyStruct) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *CrazyStruct is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&CrazyStruct{}) + +// Ensure *CrazyStruct is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&CrazyStruct{}) + +// FromStackItem retrieves fields of CrazyStruct from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *CrazyStruct) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing CrazyStruct. +// It implements [stackitem.Convertible] interface. +func (res *CrazyStruct) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewBool(res.B), error(nil) + if err != nil { + return nil, fmt.Errorf("field B: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing CrazyStruct. +// It implements [smartcontract.Convertible] interface so that CrazyStruct +// could be used with invokers. +func (res *CrazyStruct) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.B) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToSimpleStruct converts stack item into *SimpleStruct. +// NULL item is returned as nil pointer without error. +func itemToSimpleStruct(item stackitem.Item, err error) (*SimpleStruct, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(SimpleStruct) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *SimpleStruct is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&SimpleStruct{}) + +// Ensure *SimpleStruct is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&SimpleStruct{}) + +// FromStackItem retrieves fields of SimpleStruct from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *SimpleStruct) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing SimpleStruct. +// It implements [stackitem.Convertible] interface. +func (res *SimpleStruct) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 1) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing SimpleStruct. +// It implements [smartcontract.Convertible] interface so that SimpleStruct +// could be used with invokers. +func (res *SimpleStruct) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 1) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events +// with "! complicated name %$#" name from the provided [result.ApplicationLog]. +func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ComplicatedNameEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "! complicated name %$#" { + continue + } + event := new(ComplicatedNameEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or +// returns an error if it's not possible to do to so. +func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.ComplicatedParam, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ComplicatedParam: %w", err) + } + + return nil +} + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided [result.ApplicationLog]. +func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeMapEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeMap" { + continue + } + event := new(SomeMapEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeMapEvent or +// returns an error if it's not possible to do to so. +func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.M, err = func(item stackitem.Item) (map[*big.Int]map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int]map[string][]util.Uint160) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string][]util.Uint160) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field M: %w", err) + } + + return nil +} + +// SomeStructEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeStruct" name from the provided [result.ApplicationLog]. +func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeStructEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeStruct" { + continue + } + event := new(SomeStructEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeStructEvent or +// returns an error if it's not possible to do to so. +func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = itemToCrazyStruct(arr[index], nil) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} + +// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeArray" name from the provided [result.ApplicationLog]. +func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeArrayEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeArray" { + continue + } + event := new(SomeArrayEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or +// returns an error if it's not possible to do to so. +func (e *SomeArrayEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.A, err = func(item stackitem.Item) ([][]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]*big.Int, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) ([]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*big.Int, len(arr)) + for i := range res { + res[i], err = arr[i].TryInteger() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field A: %w", err) + } + + return nil +} + +// SomeUnexportedFieldEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeUnexportedField" name from the provided [result.ApplicationLog]. +func SomeUnexportedFieldEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeUnexportedFieldEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeUnexportedFieldEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeUnexportedField" { + continue + } + event := new(SomeUnexportedFieldEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeUnexportedFieldEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeUnexportedFieldEvent or +// returns an error if it's not possible to do to so. +func (e *SomeUnexportedFieldEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = itemToSimpleStruct(arr[index], nil) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/rpcbindings/notifications/rpcbindings/guessed/rpcbindings_test.go b/cli/smartcontract/rpcbindings/notifications/rpcbindings/guessed/rpcbindings_test.go new file mode 100755 index 0000000..4b01120 --- /dev/null +++ b/cli/smartcontract/rpcbindings/notifications/rpcbindings/guessed/rpcbindings_test.go @@ -0,0 +1,759 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package structs contains RPC wrappers for Notifications contract. +package structs + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// Unnamed is a contract-specific unnamed type used by its methods. +type Unnamed struct { + I *big.Int + B bool +} + +// UnnamedX is a contract-specific unnamedX type used by its methods. +type UnnamedX struct { + I *big.Int +} + +// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract. +type ComplicatedNameEvent struct { + ComplicatedParam string +} + +// SomeMapEvent represents "SomeMap" event emitted by the contract. +type SomeMapEvent struct { + M map[*big.Int][]map[string][]util.Uint160 +} + +// SomeStructEvent represents "SomeStruct" event emitted by the contract. +type SomeStructEvent struct { + S *Unnamed +} + +// SomeArrayEvent represents "SomeArray" event emitted by the contract. +type SomeArrayEvent struct { + A [][]*big.Int +} + +// SomeUnexportedFieldEvent represents "SomeUnexportedField" event emitted by the contract. +type SomeUnexportedFieldEvent struct { + S *UnnamedX +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor + hash util.Uint160 +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + return &Contract{actor, hash} +} + +// Array creates a transaction invoking `array` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Array() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "array") +} + +// ArrayTransaction creates a transaction invoking `array` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "array") +} + +// ArrayUnsigned creates a transaction invoking `array` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) ArrayUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "array", nil) +} + +// CrazyMap creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) CrazyMap() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "crazyMap") +} + +// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "crazyMap") +} + +// CrazyMapUnsigned creates a transaction invoking `crazyMap` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) CrazyMapUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "crazyMap", nil) +} + +// Main creates a transaction invoking `main` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Main() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "main") +} + +// MainTransaction creates a transaction invoking `main` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MainTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "main") +} + +// MainUnsigned creates a transaction invoking `main` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) MainUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "main", nil) +} + +// Struct creates a transaction invoking `struct` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Struct() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "struct") +} + +// StructTransaction creates a transaction invoking `struct` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) StructTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "struct") +} + +// StructUnsigned creates a transaction invoking `struct` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) StructUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "struct", nil) +} + +// UnexportedField creates a transaction invoking `unexportedField` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) UnexportedField() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "unexportedField") +} + +// UnexportedFieldTransaction creates a transaction invoking `unexportedField` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UnexportedFieldTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "unexportedField") +} + +// UnexportedFieldUnsigned creates a transaction invoking `unexportedField` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) UnexportedFieldUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "unexportedField", nil) +} + +// itemToUnnamed converts stack item into *Unnamed. +// NULL item is returned as nil pointer without error. +func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(Unnamed) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *Unnamed is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&Unnamed{}) + +// Ensure *Unnamed is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&Unnamed{}) + +// FromStackItem retrieves fields of Unnamed from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *Unnamed) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing Unnamed. +// It implements [stackitem.Convertible] interface. +func (res *Unnamed) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewBool(res.B), error(nil) + if err != nil { + return nil, fmt.Errorf("field B: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing Unnamed. +// It implements [smartcontract.Convertible] interface so that Unnamed +// could be used with invokers. +func (res *Unnamed) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.B) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToUnnamedX converts stack item into *UnnamedX. +// NULL item is returned as nil pointer without error. +func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(UnnamedX) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *UnnamedX is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&UnnamedX{}) + +// Ensure *UnnamedX is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&UnnamedX{}) + +// FromStackItem retrieves fields of UnnamedX from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *UnnamedX) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing UnnamedX. +// It implements [stackitem.Convertible] interface. +func (res *UnnamedX) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 1) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing UnnamedX. +// It implements [smartcontract.Convertible] interface so that UnnamedX +// could be used with invokers. +func (res *UnnamedX) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 1) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events +// with "! complicated name %$#" name from the provided [result.ApplicationLog]. +func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ComplicatedNameEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "! complicated name %$#" { + continue + } + event := new(ComplicatedNameEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or +// returns an error if it's not possible to do to so. +func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.ComplicatedParam, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ComplicatedParam: %w", err) + } + + return nil +} + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided [result.ApplicationLog]. +func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeMapEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeMap" { + continue + } + event := new(SomeMapEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeMapEvent or +// returns an error if it's not possible to do to so. +func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.M, err = func(item stackitem.Item) (map[*big.Int][]map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int][]map[string][]util.Uint160) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]map[string][]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]map[string][]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string][]util.Uint160) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field M: %w", err) + } + + return nil +} + +// SomeStructEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeStruct" name from the provided [result.ApplicationLog]. +func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeStructEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeStruct" { + continue + } + event := new(SomeStructEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeStructEvent or +// returns an error if it's not possible to do to so. +func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = itemToUnnamed(arr[index], nil) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} + +// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeArray" name from the provided [result.ApplicationLog]. +func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeArrayEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeArray" { + continue + } + event := new(SomeArrayEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or +// returns an error if it's not possible to do to so. +func (e *SomeArrayEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.A, err = func(item stackitem.Item) ([][]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]*big.Int, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) ([]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*big.Int, len(arr)) + for i := range res { + res[i], err = arr[i].TryInteger() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field A: %w", err) + } + + return nil +} + +// SomeUnexportedFieldEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeUnexportedField" name from the provided [result.ApplicationLog]. +func SomeUnexportedFieldEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeUnexportedFieldEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeUnexportedFieldEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeUnexportedField" { + continue + } + event := new(SomeUnexportedFieldEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeUnexportedFieldEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeUnexportedFieldEvent or +// returns an error if it's not possible to do to so. +func (e *SomeUnexportedFieldEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = itemToUnnamedX(arr[index], nil) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/rpcbindings/notifications/rpcbindings/rpcbindings_test.go b/cli/smartcontract/rpcbindings/notifications/rpcbindings/rpcbindings_test.go new file mode 100644 index 0000000..5b72cbd --- /dev/null +++ b/cli/smartcontract/rpcbindings/notifications/rpcbindings/rpcbindings_test.go @@ -0,0 +1,500 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package structs contains RPC wrappers for Notifications contract. +package structs + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract. +type ComplicatedNameEvent struct { + ComplicatedParam string +} + +// SomeMapEvent represents "SomeMap" event emitted by the contract. +type SomeMapEvent struct { + M map[any]any +} + +// SomeStructEvent represents "SomeStruct" event emitted by the contract. +type SomeStructEvent struct { + S []any +} + +// SomeArrayEvent represents "SomeArray" event emitted by the contract. +type SomeArrayEvent struct { + A []any +} + +// SomeUnexportedFieldEvent represents "SomeUnexportedField" event emitted by the contract. +type SomeUnexportedFieldEvent struct { + S []any +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor + hash util.Uint160 +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + return &Contract{actor, hash} +} + +// Array creates a transaction invoking `array` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Array() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "array") +} + +// ArrayTransaction creates a transaction invoking `array` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "array") +} + +// ArrayUnsigned creates a transaction invoking `array` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) ArrayUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "array", nil) +} + +// CrazyMap creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) CrazyMap() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "crazyMap") +} + +// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "crazyMap") +} + +// CrazyMapUnsigned creates a transaction invoking `crazyMap` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) CrazyMapUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "crazyMap", nil) +} + +// Main creates a transaction invoking `main` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Main() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "main") +} + +// MainTransaction creates a transaction invoking `main` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MainTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "main") +} + +// MainUnsigned creates a transaction invoking `main` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) MainUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "main", nil) +} + +// Struct creates a transaction invoking `struct` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Struct() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "struct") +} + +// StructTransaction creates a transaction invoking `struct` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) StructTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "struct") +} + +// StructUnsigned creates a transaction invoking `struct` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) StructUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "struct", nil) +} + +// UnexportedField creates a transaction invoking `unexportedField` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) UnexportedField() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "unexportedField") +} + +// UnexportedFieldTransaction creates a transaction invoking `unexportedField` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UnexportedFieldTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "unexportedField") +} + +// UnexportedFieldUnsigned creates a transaction invoking `unexportedField` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) UnexportedFieldUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "unexportedField", nil) +} + +// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events +// with "! complicated name %$#" name from the provided [result.ApplicationLog]. +func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ComplicatedNameEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "! complicated name %$#" { + continue + } + event := new(ComplicatedNameEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or +// returns an error if it's not possible to do to so. +func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.ComplicatedParam, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ComplicatedParam: %w", err) + } + + return nil +} + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided [result.ApplicationLog]. +func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeMapEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeMap" { + continue + } + event := new(SomeMapEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeMapEvent or +// returns an error if it's not possible to do to so. +func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.M, err = func(item stackitem.Item) (map[any]any, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[any]any) + for i := range m { + k, err := m[i].Key.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := m[i].Value.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field M: %w", err) + } + + return nil +} + +// SomeStructEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeStruct" name from the provided [result.ApplicationLog]. +func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeStructEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeStruct" { + continue + } + event := new(SomeStructEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeStructEvent or +// returns an error if it's not possible to do to so. +func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = func(item stackitem.Item) ([]any, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]any, len(arr)) + for i := range res { + res[i], err = arr[i].Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} + +// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeArray" name from the provided [result.ApplicationLog]. +func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeArrayEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeArray" { + continue + } + event := new(SomeArrayEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or +// returns an error if it's not possible to do to so. +func (e *SomeArrayEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.A, err = func(item stackitem.Item) ([]any, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]any, len(arr)) + for i := range res { + res[i], err = arr[i].Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field A: %w", err) + } + + return nil +} + +// SomeUnexportedFieldEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeUnexportedField" name from the provided [result.ApplicationLog]. +func SomeUnexportedFieldEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeUnexportedFieldEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeUnexportedFieldEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeUnexportedField" { + continue + } + event := new(SomeUnexportedFieldEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeUnexportedFieldEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeUnexportedFieldEvent or +// returns an error if it's not possible to do to so. +func (e *SomeUnexportedFieldEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = func(item stackitem.Item) ([]any, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]any, len(arr)) + for i := range res { + res[i], err = arr[i].Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/rpcbindings/royalty/rpcbindings/dynamic_hash/rpcbindings_test.go b/cli/smartcontract/rpcbindings/royalty/rpcbindings/dynamic_hash/rpcbindings_test.go new file mode 100644 index 0000000..9ead45f --- /dev/null +++ b/cli/smartcontract/rpcbindings/royalty/rpcbindings/dynamic_hash/rpcbindings_test.go @@ -0,0 +1,53 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package royalty contains RPC wrappers for Test royalty contract. +package royalty + +import ( + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/util" + "math/big" +) + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor + hash util.Uint160 +} + +// New creates an instance of Contract using provided contract hash and the given Actor. +func New(actor Actor, hash util.Uint160) *Contract { + return &Contract{actor, hash} +} + +// RoyaltiesTransferred creates a transaction invoking `royaltiesTransferred` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) RoyaltiesTransferred(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount) +} + +// RoyaltiesTransferredTransaction creates a transaction invoking `royaltiesTransferred` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) RoyaltiesTransferredTransaction(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount) +} + +// RoyaltiesTransferredUnsigned creates a transaction invoking `royaltiesTransferred` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) RoyaltiesTransferredUnsigned(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "royaltiesTransferred", nil, royaltyToken, royaltyRecipient, buyer, tokenId, amount) +} diff --git a/cli/smartcontract/rpcbindings/royalty/rpcbindings/rpcbindings_test.go b/cli/smartcontract/rpcbindings/royalty/rpcbindings/rpcbindings_test.go new file mode 100644 index 0000000..ad3fe68 --- /dev/null +++ b/cli/smartcontract/rpcbindings/royalty/rpcbindings/rpcbindings_test.go @@ -0,0 +1,57 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package royalty contains RPC wrappers for Test royalty contract. +package royalty + +import ( + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/util" + "math/big" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor + hash util.Uint160 +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + return &Contract{actor, hash} +} + +// RoyaltiesTransferred creates a transaction invoking `royaltiesTransferred` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) RoyaltiesTransferred(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount) +} + +// RoyaltiesTransferredTransaction creates a transaction invoking `royaltiesTransferred` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) RoyaltiesTransferredTransaction(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount) +} + +// RoyaltiesTransferredUnsigned creates a transaction invoking `royaltiesTransferred` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) RoyaltiesTransferredUnsigned(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "royaltiesTransferred", nil, royaltyToken, royaltyRecipient, buyer, tokenId, amount) +} diff --git a/cli/smartcontract/rpcbindings/structs/rpcbindings/dynamic_hash/rpcbindings_test.go b/cli/smartcontract/rpcbindings/structs/rpcbindings/dynamic_hash/rpcbindings_test.go new file mode 100755 index 0000000..d98de5d --- /dev/null +++ b/cli/smartcontract/rpcbindings/structs/rpcbindings/dynamic_hash/rpcbindings_test.go @@ -0,0 +1,2860 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package structs contains RPC wrappers for Types contract. +package structs + +import ( + "crypto/elliptic" + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// LedgerBlock is a contract-specific ledger.Block type used by its methods. +type LedgerBlock struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + PrimaryIndex *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int +} + +// LedgerTransaction is a contract-specific ledger.Transaction type used by its methods. +type LedgerTransaction struct { + Hash util.Uint256 + Version *big.Int + Nonce *big.Int + Sender util.Uint160 + SysFee *big.Int + NetFee *big.Int + ValidUntilBlock *big.Int + Script []byte +} + +// ManagementABI is a contract-specific management.ABI type used by its methods. +type ManagementABI struct { + Methods []*ManagementMethod + Events []*ManagementEvent +} + +// ManagementContract is a contract-specific management.Contract type used by its methods. +type ManagementContract struct { + ID *big.Int + UpdateCounter *big.Int + Hash util.Uint160 + NEF []byte + Manifest *ManagementManifest +} + +// ManagementEvent is a contract-specific management.Event type used by its methods. +type ManagementEvent struct { + Name string + Params []*ManagementParameter +} + +// ManagementGroup is a contract-specific management.Group type used by its methods. +type ManagementGroup struct { + PublicKey *keys.PublicKey + Signature []byte +} + +// ManagementManifest is a contract-specific management.Manifest type used by its methods. +type ManagementManifest struct { + Name string + Groups []*ManagementGroup + Features map[string]string + SupportedStandards []string + ABI *ManagementABI + Permissions []*ManagementPermission + Trusts []util.Uint160 + Extra any +} + +// ManagementMethod is a contract-specific management.Method type used by its methods. +type ManagementMethod struct { + Name string + Params []*ManagementParameter + ReturnType *big.Int + Offset *big.Int + Safe bool +} + +// ManagementParameter is a contract-specific management.Parameter type used by its methods. +type ManagementParameter struct { + Name string + Type *big.Int +} + +// ManagementPermission is a contract-specific management.Permission type used by its methods. +type ManagementPermission struct { + Contract util.Uint160 + Methods []string +} + +// StructsInternal is a contract-specific structs.Internal type used by its methods. +type StructsInternal struct { + Bool bool + Int *big.Int + Bytes []byte + String string + H160 util.Uint160 + H256 util.Uint256 + PK *keys.PublicKey + PubKey *keys.PublicKey + Sign []byte + ArrOfBytes [][]byte + ArrOfH160 []util.Uint160 + Map map[*big.Int]keys.PublicKeys + Struct *StructsInternal + UnexportedField *big.Int +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker. +func NewReader(invoker Invoker, hash util.Uint160) *ContractReader { + return &ContractReader{invoker, hash} +} + +// Block invokes `block` method of contract. +func (c *ContractReader) Block(b *LedgerBlock) (*LedgerBlock, error) { + return itemToLedgerBlock(unwrap.Item(c.invoker.Call(c.hash, "block", b))) +} + +// Contract invokes `contract` method of contract. +func (c *ContractReader) Contract(mc *ManagementContract) (*ManagementContract, error) { + return itemToManagementContract(unwrap.Item(c.invoker.Call(c.hash, "contract", mc))) +} + +// Struct invokes `struct` method of contract. +func (c *ContractReader) Struct(s *StructsInternal) (*StructsInternal, error) { + return itemToStructsInternal(unwrap.Item(c.invoker.Call(c.hash, "struct", s))) +} + +// Transaction invokes `transaction` method of contract. +func (c *ContractReader) Transaction(t *LedgerTransaction) (*LedgerTransaction, error) { + return itemToLedgerTransaction(unwrap.Item(c.invoker.Call(c.hash, "transaction", t))) +} + +// itemToLedgerBlock converts stack item into *LedgerBlock. +// NULL item is returned as nil pointer without error. +func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(LedgerBlock) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *LedgerBlock is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&LedgerBlock{}) + +// Ensure *LedgerBlock is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&LedgerBlock{}) + +// FromStackItem retrieves fields of LedgerBlock from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *LedgerBlock) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 10 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.PrimaryIndex, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field PrimaryIndex: %w", err) + } + + index++ + res.NextConsensus, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing LedgerBlock. +// It implements [stackitem.Convertible] interface. +func (res *LedgerBlock) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 10) + ) + itm, err = stackitem.NewByteArray(res.Hash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Hash: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Version), error(nil) + if err != nil { + return nil, fmt.Errorf("field Version: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.PrevHash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PrevHash: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.MerkleRoot.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field MerkleRoot: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Timestamp), error(nil) + if err != nil { + return nil, fmt.Errorf("field Timestamp: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Nonce), error(nil) + if err != nil { + return nil, fmt.Errorf("field Nonce: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Index), error(nil) + if err != nil { + return nil, fmt.Errorf("field Index: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.PrimaryIndex), error(nil) + if err != nil { + return nil, fmt.Errorf("field PrimaryIndex: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.NextConsensus.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field NextConsensus: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.TransactionsLength), error(nil) + if err != nil { + return nil, fmt.Errorf("field TransactionsLength: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing LedgerBlock. +// It implements [smartcontract.Convertible] interface so that LedgerBlock +// could be used with invokers. +func (res *LedgerBlock) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 10) + ) + prm, err = smartcontract.NewParameterFromValue(res.Hash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Hash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Version) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Version: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PrevHash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PrevHash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.MerkleRoot) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field MerkleRoot: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Timestamp) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Timestamp: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Nonce) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Nonce: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Index) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Index: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PrimaryIndex) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PrimaryIndex: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.NextConsensus) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field NextConsensus: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.TransactionsLength) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field TransactionsLength: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToLedgerTransaction converts stack item into *LedgerTransaction. +// NULL item is returned as nil pointer without error. +func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(LedgerTransaction) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *LedgerTransaction is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&LedgerTransaction{}) + +// Ensure *LedgerTransaction is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&LedgerTransaction{}) + +// FromStackItem retrieves fields of LedgerTransaction from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *LedgerTransaction) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Sender, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Sender: %w", err) + } + + index++ + res.SysFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SysFee: %w", err) + } + + index++ + res.NetFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NetFee: %w", err) + } + + index++ + res.ValidUntilBlock, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ValidUntilBlock: %w", err) + } + + index++ + res.Script, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Script: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing LedgerTransaction. +// It implements [stackitem.Convertible] interface. +func (res *LedgerTransaction) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 8) + ) + itm, err = stackitem.NewByteArray(res.Hash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Hash: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Version), error(nil) + if err != nil { + return nil, fmt.Errorf("field Version: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Nonce), error(nil) + if err != nil { + return nil, fmt.Errorf("field Nonce: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Sender.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Sender: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.SysFee), error(nil) + if err != nil { + return nil, fmt.Errorf("field SysFee: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.NetFee), error(nil) + if err != nil { + return nil, fmt.Errorf("field NetFee: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.ValidUntilBlock), error(nil) + if err != nil { + return nil, fmt.Errorf("field ValidUntilBlock: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Script), error(nil) + if err != nil { + return nil, fmt.Errorf("field Script: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing LedgerTransaction. +// It implements [smartcontract.Convertible] interface so that LedgerTransaction +// could be used with invokers. +func (res *LedgerTransaction) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 8) + ) + prm, err = smartcontract.NewParameterFromValue(res.Hash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Hash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Version) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Version: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Nonce) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Nonce: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Sender) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Sender: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.SysFee) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field SysFee: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.NetFee) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field NetFee: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.ValidUntilBlock) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ValidUntilBlock: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Script) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Script: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementABI converts stack item into *ManagementABI. +// NULL item is returned as nil pointer without error. +func itemToManagementABI(item stackitem.Item, err error) (*ManagementABI, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementABI) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementABI is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementABI{}) + +// Ensure *ManagementABI is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementABI{}) + +// FromStackItem retrieves fields of ManagementABI from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementABI) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Methods, err = func(item stackitem.Item) ([]*ManagementMethod, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementMethod, len(arr)) + for i := range res { + res[i], err = itemToManagementMethod(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Methods: %w", err) + } + + index++ + res.Events, err = func(item stackitem.Item) ([]*ManagementEvent, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementEvent, len(arr)) + for i := range res { + res[i], err = itemToManagementEvent(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Events: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementABI. +// It implements [stackitem.Convertible] interface. +func (res *ManagementABI) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = func(in []*ManagementMethod) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Methods) + if err != nil { + return nil, fmt.Errorf("field Methods: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementEvent) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Events) + if err != nil { + return nil, fmt.Errorf("field Events: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementABI. +// It implements [smartcontract.Convertible] interface so that ManagementABI +// could be used with invokers. +func (res *ManagementABI) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = func(in []*ManagementMethod) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Methods) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Methods: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementEvent) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Events) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Events: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementContract converts stack item into *ManagementContract. +// NULL item is returned as nil pointer without error. +func itemToManagementContract(item stackitem.Item, err error) (*ManagementContract, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementContract) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementContract is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementContract{}) + +// Ensure *ManagementContract is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementContract{}) + +// FromStackItem retrieves fields of ManagementContract from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementContract) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.ID, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ID: %w", err) + } + + index++ + res.UpdateCounter, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field UpdateCounter: %w", err) + } + + index++ + res.Hash, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.NEF, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field NEF: %w", err) + } + + index++ + res.Manifest, err = itemToManagementManifest(arr[index], nil) + if err != nil { + return fmt.Errorf("field Manifest: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementContract. +// It implements [stackitem.Convertible] interface. +func (res *ManagementContract) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 5) + ) + itm, err = (*stackitem.BigInteger)(res.ID), error(nil) + if err != nil { + return nil, fmt.Errorf("field ID: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.UpdateCounter), error(nil) + if err != nil { + return nil, fmt.Errorf("field UpdateCounter: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Hash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Hash: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.NEF), error(nil) + if err != nil { + return nil, fmt.Errorf("field NEF: %w", err) + } + items = append(items, itm) + + itm, err = res.Manifest.ToStackItem() + if err != nil { + return nil, fmt.Errorf("field Manifest: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementContract. +// It implements [smartcontract.Convertible] interface so that ManagementContract +// could be used with invokers. +func (res *ManagementContract) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 5) + ) + prm, err = smartcontract.NewParameterFromValue(res.ID) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ID: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.UpdateCounter) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field UpdateCounter: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Hash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Hash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.NEF) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field NEF: %w", err) + } + prms = append(prms, prm) + + prm, err = res.Manifest.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Manifest: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementEvent converts stack item into *ManagementEvent. +// NULL item is returned as nil pointer without error. +func itemToManagementEvent(item stackitem.Item, err error) (*ManagementEvent, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementEvent) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementEvent is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementEvent{}) + +// Ensure *ManagementEvent is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementEvent{}) + +// FromStackItem retrieves fields of ManagementEvent from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementEvent) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Params, err = func(item stackitem.Item) ([]*ManagementParameter, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementParameter, len(arr)) + for i := range res { + res[i], err = itemToManagementParameter(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Params: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementEvent. +// It implements [stackitem.Convertible] interface. +func (res *ManagementEvent) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementParameter) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Params) + if err != nil { + return nil, fmt.Errorf("field Params: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementEvent. +// It implements [smartcontract.Convertible] interface so that ManagementEvent +// could be used with invokers. +func (res *ManagementEvent) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementParameter) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Params) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Params: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementGroup converts stack item into *ManagementGroup. +// NULL item is returned as nil pointer without error. +func itemToManagementGroup(item stackitem.Item, err error) (*ManagementGroup, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementGroup) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementGroup is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementGroup{}) + +// Ensure *ManagementGroup is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementGroup{}) + +// FromStackItem retrieves fields of ManagementGroup from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementGroup) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.PublicKey, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PublicKey: %w", err) + } + + index++ + res.Signature, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Signature: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementGroup. +// It implements [stackitem.Convertible] interface. +func (res *ManagementGroup) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray(res.PublicKey.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PublicKey: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Signature), error(nil) + if err != nil { + return nil, fmt.Errorf("field Signature: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementGroup. +// It implements [smartcontract.Convertible] interface so that ManagementGroup +// could be used with invokers. +func (res *ManagementGroup) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.PublicKey) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PublicKey: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Signature) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Signature: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementManifest converts stack item into *ManagementManifest. +// NULL item is returned as nil pointer without error. +func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManifest, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementManifest) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementManifest is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementManifest{}) + +// Ensure *ManagementManifest is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementManifest{}) + +// FromStackItem retrieves fields of ManagementManifest from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementManifest) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Groups, err = func(item stackitem.Item) ([]*ManagementGroup, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementGroup, len(arr)) + for i := range res { + res[i], err = itemToManagementGroup(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Groups: %w", err) + } + + index++ + res.Features, err = func(item stackitem.Item) (map[string]string, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string]string) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Features: %w", err) + } + + index++ + res.SupportedStandards, err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field SupportedStandards: %w", err) + } + + index++ + res.ABI, err = itemToManagementABI(arr[index], nil) + if err != nil { + return fmt.Errorf("field ABI: %w", err) + } + + index++ + res.Permissions, err = func(item stackitem.Item) ([]*ManagementPermission, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementPermission, len(arr)) + for i := range res { + res[i], err = itemToManagementPermission(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Permissions: %w", err) + } + + index++ + res.Trusts, err = func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Trusts: %w", err) + } + + index++ + res.Extra, err = arr[index].Value(), error(nil) + if err != nil { + return fmt.Errorf("field Extra: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementManifest. +// It implements [stackitem.Convertible] interface. +func (res *ManagementManifest) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 8) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementGroup) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Groups) + if err != nil { + return nil, fmt.Errorf("field Groups: %w", err) + } + items = append(items, itm) + + itm, err = func(in map[string]string) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var m = stackitem.NewMap() + for k, v := range in { + iKey, err := stackitem.NewByteArray([]byte(k)), error(nil) + if err != nil { + return nil, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := stackitem.NewByteArray([]byte(v)), error(nil) + if err != nil { + return nil, fmt.Errorf("key %v, wrong value: %w", k, err) + } + m.Add(iKey, iVal) + } + return m, nil + }(res.Features) + if err != nil { + return nil, fmt.Errorf("field Features: %w", err) + } + items = append(items, itm) + + itm, err = func(in []string) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray([]byte(v)), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.SupportedStandards) + if err != nil { + return nil, fmt.Errorf("field SupportedStandards: %w", err) + } + items = append(items, itm) + + itm, err = res.ABI.ToStackItem() + if err != nil { + return nil, fmt.Errorf("field ABI: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementPermission) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Permissions) + if err != nil { + return nil, fmt.Errorf("field Permissions: %w", err) + } + items = append(items, itm) + + itm, err = func(in []util.Uint160) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Trusts) + if err != nil { + return nil, fmt.Errorf("field Trusts: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.TryMake(res.Extra) + if err != nil { + return nil, fmt.Errorf("field Extra: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementManifest. +// It implements [smartcontract.Convertible] interface so that ManagementManifest +// could be used with invokers. +func (res *ManagementManifest) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 8) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementGroup) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Groups) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Groups: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in map[string]string) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.ParameterPair, 0, len(in)) + for k, v := range in { + iKey, err := smartcontract.NewParameterFromValue(k) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v, wrong value: %w", k, err) + } + prms = append(prms, smartcontract.ParameterPair{Key: iKey, Value: iVal}) + } + return smartcontract.Parameter{Type: smartcontract.MapType, Value: prms}, nil + }(res.Features) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Features: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []string) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.SupportedStandards) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field SupportedStandards: %w", err) + } + prms = append(prms, prm) + + prm, err = res.ABI.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ABI: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementPermission) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Permissions) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Permissions: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []util.Uint160) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Trusts) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Trusts: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Extra) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Extra: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementMethod converts stack item into *ManagementMethod. +// NULL item is returned as nil pointer without error. +func itemToManagementMethod(item stackitem.Item, err error) (*ManagementMethod, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementMethod) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementMethod is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementMethod{}) + +// Ensure *ManagementMethod is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementMethod{}) + +// FromStackItem retrieves fields of ManagementMethod from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementMethod) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Params, err = func(item stackitem.Item) ([]*ManagementParameter, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementParameter, len(arr)) + for i := range res { + res[i], err = itemToManagementParameter(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Params: %w", err) + } + + index++ + res.ReturnType, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ReturnType: %w", err) + } + + index++ + res.Offset, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Offset: %w", err) + } + + index++ + res.Safe, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field Safe: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementMethod. +// It implements [stackitem.Convertible] interface. +func (res *ManagementMethod) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 5) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementParameter) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Params) + if err != nil { + return nil, fmt.Errorf("field Params: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.ReturnType), error(nil) + if err != nil { + return nil, fmt.Errorf("field ReturnType: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Offset), error(nil) + if err != nil { + return nil, fmt.Errorf("field Offset: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewBool(res.Safe), error(nil) + if err != nil { + return nil, fmt.Errorf("field Safe: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementMethod. +// It implements [smartcontract.Convertible] interface so that ManagementMethod +// could be used with invokers. +func (res *ManagementMethod) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 5) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementParameter) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Params) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Params: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.ReturnType) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ReturnType: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Offset) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Offset: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Safe) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Safe: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementParameter converts stack item into *ManagementParameter. +// NULL item is returned as nil pointer without error. +func itemToManagementParameter(item stackitem.Item, err error) (*ManagementParameter, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementParameter) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementParameter is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementParameter{}) + +// Ensure *ManagementParameter is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementParameter{}) + +// FromStackItem retrieves fields of ManagementParameter from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementParameter) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Type, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Type: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementParameter. +// It implements [stackitem.Convertible] interface. +func (res *ManagementParameter) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Type), error(nil) + if err != nil { + return nil, fmt.Errorf("field Type: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementParameter. +// It implements [smartcontract.Convertible] interface so that ManagementParameter +// could be used with invokers. +func (res *ManagementParameter) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Type) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Type: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementPermission converts stack item into *ManagementPermission. +// NULL item is returned as nil pointer without error. +func itemToManagementPermission(item stackitem.Item, err error) (*ManagementPermission, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementPermission) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementPermission is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementPermission{}) + +// Ensure *ManagementPermission is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementPermission{}) + +// FromStackItem retrieves fields of ManagementPermission from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementPermission) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Contract, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Contract: %w", err) + } + + index++ + res.Methods, err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Methods: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementPermission. +// It implements [stackitem.Convertible] interface. +func (res *ManagementPermission) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray(res.Contract.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Contract: %w", err) + } + items = append(items, itm) + + itm, err = func(in []string) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray([]byte(v)), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Methods) + if err != nil { + return nil, fmt.Errorf("field Methods: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementPermission. +// It implements [smartcontract.Convertible] interface so that ManagementPermission +// could be used with invokers. +func (res *ManagementPermission) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Contract) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Contract: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []string) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Methods) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Methods: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToStructsInternal converts stack item into *StructsInternal. +// NULL item is returned as nil pointer without error. +func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(StructsInternal) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *StructsInternal is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&StructsInternal{}) + +// Ensure *StructsInternal is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&StructsInternal{}) + +// FromStackItem retrieves fields of StructsInternal from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *StructsInternal) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 14 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Bool, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field Bool: %w", err) + } + + index++ + res.Int, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Int: %w", err) + } + + index++ + res.Bytes, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Bytes: %w", err) + } + + index++ + res.String, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field String: %w", err) + } + + index++ + res.H160, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field H160: %w", err) + } + + index++ + res.H256, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field H256: %w", err) + } + + index++ + res.PK, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PK: %w", err) + } + + index++ + res.PubKey, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PubKey: %w", err) + } + + index++ + res.Sign, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Sign: %w", err) + } + + index++ + res.ArrOfBytes, err = func(item stackitem.Item) ([][]byte, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]byte, len(arr)) + for i := range res { + res[i], err = arr[i].TryBytes() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ArrOfBytes: %w", err) + } + + index++ + res.ArrOfH160, err = func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ArrOfH160: %w", err) + } + + index++ + res.Map, err = func(item stackitem.Item) (map[*big.Int]keys.PublicKeys, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int]keys.PublicKeys) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (keys.PublicKeys, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make(keys.PublicKeys, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Map: %w", err) + } + + index++ + res.Struct, err = itemToStructsInternal(arr[index], nil) + if err != nil { + return fmt.Errorf("field Struct: %w", err) + } + + index++ + res.UnexportedField, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field UnexportedField: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing StructsInternal. +// It implements [stackitem.Convertible] interface. +func (res *StructsInternal) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 14) + ) + itm, err = stackitem.NewBool(res.Bool), error(nil) + if err != nil { + return nil, fmt.Errorf("field Bool: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Int), error(nil) + if err != nil { + return nil, fmt.Errorf("field Int: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Bytes), error(nil) + if err != nil { + return nil, fmt.Errorf("field Bytes: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray([]byte(res.String)), error(nil) + if err != nil { + return nil, fmt.Errorf("field String: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.H160.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field H160: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.H256.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field H256: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.PK.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PK: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.PubKey.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PubKey: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Sign), error(nil) + if err != nil { + return nil, fmt.Errorf("field Sign: %w", err) + } + items = append(items, itm) + + itm, err = func(in [][]byte) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.ArrOfBytes) + if err != nil { + return nil, fmt.Errorf("field ArrOfBytes: %w", err) + } + items = append(items, itm) + + itm, err = func(in []util.Uint160) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.ArrOfH160) + if err != nil { + return nil, fmt.Errorf("field ArrOfH160: %w", err) + } + items = append(items, itm) + + itm, err = func(in map[*big.Int]keys.PublicKeys) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var m = stackitem.NewMap() + for k, v := range in { + iKey, err := (*stackitem.BigInteger)(k), error(nil) + if err != nil { + return nil, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := func(in keys.PublicKeys) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(v) + if err != nil { + return nil, fmt.Errorf("key %v, wrong value: %w", k, err) + } + m.Add(iKey, iVal) + } + return m, nil + }(res.Map) + if err != nil { + return nil, fmt.Errorf("field Map: %w", err) + } + items = append(items, itm) + + itm, err = res.Struct.ToStackItem() + if err != nil { + return nil, fmt.Errorf("field Struct: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.UnexportedField), error(nil) + if err != nil { + return nil, fmt.Errorf("field UnexportedField: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing StructsInternal. +// It implements [smartcontract.Convertible] interface so that StructsInternal +// could be used with invokers. +func (res *StructsInternal) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 14) + ) + prm, err = smartcontract.NewParameterFromValue(res.Bool) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Bool: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Int) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Int: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Bytes) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Bytes: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.String) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field String: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.H160) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field H160: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.H256) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field H256: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PK) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PK: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PubKey) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PubKey: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Sign) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Sign: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in [][]byte) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.ArrOfBytes) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ArrOfBytes: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []util.Uint160) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.ArrOfH160) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ArrOfH160: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in map[*big.Int]keys.PublicKeys) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.ParameterPair, 0, len(in)) + for k, v := range in { + iKey, err := smartcontract.NewParameterFromValue(k) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := func(in keys.PublicKeys) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v, wrong value: %w", k, err) + } + prms = append(prms, smartcontract.ParameterPair{Key: iKey, Value: iVal}) + } + return smartcontract.Parameter{Type: smartcontract.MapType, Value: prms}, nil + }(res.Map) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Map: %w", err) + } + prms = append(prms, prm) + + prm, err = res.Struct.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Struct: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.UnexportedField) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field UnexportedField: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} diff --git a/cli/smartcontract/rpcbindings/structs/rpcbindings/rpcbindings_test.go b/cli/smartcontract/rpcbindings/structs/rpcbindings/rpcbindings_test.go new file mode 100644 index 0000000..9dcb21c --- /dev/null +++ b/cli/smartcontract/rpcbindings/structs/rpcbindings/rpcbindings_test.go @@ -0,0 +1,2864 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package structs contains RPC wrappers for Types contract. +package structs + +import ( + "crypto/elliptic" + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// LedgerBlock is a contract-specific ledger.Block type used by its methods. +type LedgerBlock struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + PrimaryIndex *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int +} + +// LedgerTransaction is a contract-specific ledger.Transaction type used by its methods. +type LedgerTransaction struct { + Hash util.Uint256 + Version *big.Int + Nonce *big.Int + Sender util.Uint160 + SysFee *big.Int + NetFee *big.Int + ValidUntilBlock *big.Int + Script []byte +} + +// ManagementABI is a contract-specific management.ABI type used by its methods. +type ManagementABI struct { + Methods []*ManagementMethod + Events []*ManagementEvent +} + +// ManagementContract is a contract-specific management.Contract type used by its methods. +type ManagementContract struct { + ID *big.Int + UpdateCounter *big.Int + Hash util.Uint160 + NEF []byte + Manifest *ManagementManifest +} + +// ManagementEvent is a contract-specific management.Event type used by its methods. +type ManagementEvent struct { + Name string + Params []*ManagementParameter +} + +// ManagementGroup is a contract-specific management.Group type used by its methods. +type ManagementGroup struct { + PublicKey *keys.PublicKey + Signature []byte +} + +// ManagementManifest is a contract-specific management.Manifest type used by its methods. +type ManagementManifest struct { + Name string + Groups []*ManagementGroup + Features map[string]string + SupportedStandards []string + ABI *ManagementABI + Permissions []*ManagementPermission + Trusts []util.Uint160 + Extra any +} + +// ManagementMethod is a contract-specific management.Method type used by its methods. +type ManagementMethod struct { + Name string + Params []*ManagementParameter + ReturnType *big.Int + Offset *big.Int + Safe bool +} + +// ManagementParameter is a contract-specific management.Parameter type used by its methods. +type ManagementParameter struct { + Name string + Type *big.Int +} + +// ManagementPermission is a contract-specific management.Permission type used by its methods. +type ManagementPermission struct { + Contract util.Uint160 + Methods []string +} + +// StructsInternal is a contract-specific structs.Internal type used by its methods. +type StructsInternal struct { + Bool bool + Int *big.Int + Bytes []byte + String string + H160 util.Uint160 + H256 util.Uint256 + PK *keys.PublicKey + PubKey *keys.PublicKey + Sign []byte + ArrOfBytes [][]byte + ArrOfH160 []util.Uint160 + Map map[*big.Int]keys.PublicKeys + Struct *StructsInternal + UnexportedField *big.Int +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{invoker, hash} +} + +// Block invokes `block` method of contract. +func (c *ContractReader) Block(b *LedgerBlock) (*LedgerBlock, error) { + return itemToLedgerBlock(unwrap.Item(c.invoker.Call(c.hash, "block", b))) +} + +// Contract invokes `contract` method of contract. +func (c *ContractReader) Contract(mc *ManagementContract) (*ManagementContract, error) { + return itemToManagementContract(unwrap.Item(c.invoker.Call(c.hash, "contract", mc))) +} + +// Struct invokes `struct` method of contract. +func (c *ContractReader) Struct(s *StructsInternal) (*StructsInternal, error) { + return itemToStructsInternal(unwrap.Item(c.invoker.Call(c.hash, "struct", s))) +} + +// Transaction invokes `transaction` method of contract. +func (c *ContractReader) Transaction(t *LedgerTransaction) (*LedgerTransaction, error) { + return itemToLedgerTransaction(unwrap.Item(c.invoker.Call(c.hash, "transaction", t))) +} + +// itemToLedgerBlock converts stack item into *LedgerBlock. +// NULL item is returned as nil pointer without error. +func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(LedgerBlock) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *LedgerBlock is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&LedgerBlock{}) + +// Ensure *LedgerBlock is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&LedgerBlock{}) + +// FromStackItem retrieves fields of LedgerBlock from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *LedgerBlock) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 10 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.PrimaryIndex, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field PrimaryIndex: %w", err) + } + + index++ + res.NextConsensus, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing LedgerBlock. +// It implements [stackitem.Convertible] interface. +func (res *LedgerBlock) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 10) + ) + itm, err = stackitem.NewByteArray(res.Hash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Hash: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Version), error(nil) + if err != nil { + return nil, fmt.Errorf("field Version: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.PrevHash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PrevHash: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.MerkleRoot.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field MerkleRoot: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Timestamp), error(nil) + if err != nil { + return nil, fmt.Errorf("field Timestamp: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Nonce), error(nil) + if err != nil { + return nil, fmt.Errorf("field Nonce: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Index), error(nil) + if err != nil { + return nil, fmt.Errorf("field Index: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.PrimaryIndex), error(nil) + if err != nil { + return nil, fmt.Errorf("field PrimaryIndex: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.NextConsensus.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field NextConsensus: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.TransactionsLength), error(nil) + if err != nil { + return nil, fmt.Errorf("field TransactionsLength: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing LedgerBlock. +// It implements [smartcontract.Convertible] interface so that LedgerBlock +// could be used with invokers. +func (res *LedgerBlock) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 10) + ) + prm, err = smartcontract.NewParameterFromValue(res.Hash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Hash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Version) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Version: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PrevHash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PrevHash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.MerkleRoot) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field MerkleRoot: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Timestamp) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Timestamp: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Nonce) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Nonce: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Index) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Index: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PrimaryIndex) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PrimaryIndex: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.NextConsensus) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field NextConsensus: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.TransactionsLength) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field TransactionsLength: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToLedgerTransaction converts stack item into *LedgerTransaction. +// NULL item is returned as nil pointer without error. +func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(LedgerTransaction) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *LedgerTransaction is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&LedgerTransaction{}) + +// Ensure *LedgerTransaction is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&LedgerTransaction{}) + +// FromStackItem retrieves fields of LedgerTransaction from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *LedgerTransaction) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Sender, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Sender: %w", err) + } + + index++ + res.SysFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SysFee: %w", err) + } + + index++ + res.NetFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NetFee: %w", err) + } + + index++ + res.ValidUntilBlock, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ValidUntilBlock: %w", err) + } + + index++ + res.Script, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Script: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing LedgerTransaction. +// It implements [stackitem.Convertible] interface. +func (res *LedgerTransaction) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 8) + ) + itm, err = stackitem.NewByteArray(res.Hash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Hash: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Version), error(nil) + if err != nil { + return nil, fmt.Errorf("field Version: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Nonce), error(nil) + if err != nil { + return nil, fmt.Errorf("field Nonce: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Sender.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Sender: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.SysFee), error(nil) + if err != nil { + return nil, fmt.Errorf("field SysFee: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.NetFee), error(nil) + if err != nil { + return nil, fmt.Errorf("field NetFee: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.ValidUntilBlock), error(nil) + if err != nil { + return nil, fmt.Errorf("field ValidUntilBlock: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Script), error(nil) + if err != nil { + return nil, fmt.Errorf("field Script: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing LedgerTransaction. +// It implements [smartcontract.Convertible] interface so that LedgerTransaction +// could be used with invokers. +func (res *LedgerTransaction) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 8) + ) + prm, err = smartcontract.NewParameterFromValue(res.Hash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Hash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Version) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Version: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Nonce) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Nonce: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Sender) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Sender: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.SysFee) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field SysFee: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.NetFee) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field NetFee: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.ValidUntilBlock) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ValidUntilBlock: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Script) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Script: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementABI converts stack item into *ManagementABI. +// NULL item is returned as nil pointer without error. +func itemToManagementABI(item stackitem.Item, err error) (*ManagementABI, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementABI) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementABI is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementABI{}) + +// Ensure *ManagementABI is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementABI{}) + +// FromStackItem retrieves fields of ManagementABI from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementABI) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Methods, err = func(item stackitem.Item) ([]*ManagementMethod, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementMethod, len(arr)) + for i := range res { + res[i], err = itemToManagementMethod(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Methods: %w", err) + } + + index++ + res.Events, err = func(item stackitem.Item) ([]*ManagementEvent, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementEvent, len(arr)) + for i := range res { + res[i], err = itemToManagementEvent(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Events: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementABI. +// It implements [stackitem.Convertible] interface. +func (res *ManagementABI) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = func(in []*ManagementMethod) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Methods) + if err != nil { + return nil, fmt.Errorf("field Methods: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementEvent) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Events) + if err != nil { + return nil, fmt.Errorf("field Events: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementABI. +// It implements [smartcontract.Convertible] interface so that ManagementABI +// could be used with invokers. +func (res *ManagementABI) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = func(in []*ManagementMethod) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Methods) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Methods: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementEvent) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Events) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Events: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementContract converts stack item into *ManagementContract. +// NULL item is returned as nil pointer without error. +func itemToManagementContract(item stackitem.Item, err error) (*ManagementContract, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementContract) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementContract is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementContract{}) + +// Ensure *ManagementContract is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementContract{}) + +// FromStackItem retrieves fields of ManagementContract from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementContract) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.ID, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ID: %w", err) + } + + index++ + res.UpdateCounter, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field UpdateCounter: %w", err) + } + + index++ + res.Hash, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.NEF, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field NEF: %w", err) + } + + index++ + res.Manifest, err = itemToManagementManifest(arr[index], nil) + if err != nil { + return fmt.Errorf("field Manifest: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementContract. +// It implements [stackitem.Convertible] interface. +func (res *ManagementContract) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 5) + ) + itm, err = (*stackitem.BigInteger)(res.ID), error(nil) + if err != nil { + return nil, fmt.Errorf("field ID: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.UpdateCounter), error(nil) + if err != nil { + return nil, fmt.Errorf("field UpdateCounter: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Hash.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Hash: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.NEF), error(nil) + if err != nil { + return nil, fmt.Errorf("field NEF: %w", err) + } + items = append(items, itm) + + itm, err = res.Manifest.ToStackItem() + if err != nil { + return nil, fmt.Errorf("field Manifest: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementContract. +// It implements [smartcontract.Convertible] interface so that ManagementContract +// could be used with invokers. +func (res *ManagementContract) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 5) + ) + prm, err = smartcontract.NewParameterFromValue(res.ID) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ID: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.UpdateCounter) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field UpdateCounter: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Hash) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Hash: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.NEF) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field NEF: %w", err) + } + prms = append(prms, prm) + + prm, err = res.Manifest.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Manifest: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementEvent converts stack item into *ManagementEvent. +// NULL item is returned as nil pointer without error. +func itemToManagementEvent(item stackitem.Item, err error) (*ManagementEvent, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementEvent) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementEvent is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementEvent{}) + +// Ensure *ManagementEvent is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementEvent{}) + +// FromStackItem retrieves fields of ManagementEvent from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementEvent) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Params, err = func(item stackitem.Item) ([]*ManagementParameter, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementParameter, len(arr)) + for i := range res { + res[i], err = itemToManagementParameter(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Params: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementEvent. +// It implements [stackitem.Convertible] interface. +func (res *ManagementEvent) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementParameter) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Params) + if err != nil { + return nil, fmt.Errorf("field Params: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementEvent. +// It implements [smartcontract.Convertible] interface so that ManagementEvent +// could be used with invokers. +func (res *ManagementEvent) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementParameter) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Params) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Params: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementGroup converts stack item into *ManagementGroup. +// NULL item is returned as nil pointer without error. +func itemToManagementGroup(item stackitem.Item, err error) (*ManagementGroup, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementGroup) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementGroup is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementGroup{}) + +// Ensure *ManagementGroup is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementGroup{}) + +// FromStackItem retrieves fields of ManagementGroup from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementGroup) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.PublicKey, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PublicKey: %w", err) + } + + index++ + res.Signature, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Signature: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementGroup. +// It implements [stackitem.Convertible] interface. +func (res *ManagementGroup) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray(res.PublicKey.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PublicKey: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Signature), error(nil) + if err != nil { + return nil, fmt.Errorf("field Signature: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementGroup. +// It implements [smartcontract.Convertible] interface so that ManagementGroup +// could be used with invokers. +func (res *ManagementGroup) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.PublicKey) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PublicKey: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Signature) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Signature: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementManifest converts stack item into *ManagementManifest. +// NULL item is returned as nil pointer without error. +func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManifest, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementManifest) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementManifest is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementManifest{}) + +// Ensure *ManagementManifest is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementManifest{}) + +// FromStackItem retrieves fields of ManagementManifest from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementManifest) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Groups, err = func(item stackitem.Item) ([]*ManagementGroup, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementGroup, len(arr)) + for i := range res { + res[i], err = itemToManagementGroup(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Groups: %w", err) + } + + index++ + res.Features, err = func(item stackitem.Item) (map[string]string, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string]string) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Features: %w", err) + } + + index++ + res.SupportedStandards, err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field SupportedStandards: %w", err) + } + + index++ + res.ABI, err = itemToManagementABI(arr[index], nil) + if err != nil { + return fmt.Errorf("field ABI: %w", err) + } + + index++ + res.Permissions, err = func(item stackitem.Item) ([]*ManagementPermission, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementPermission, len(arr)) + for i := range res { + res[i], err = itemToManagementPermission(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Permissions: %w", err) + } + + index++ + res.Trusts, err = func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Trusts: %w", err) + } + + index++ + res.Extra, err = arr[index].Value(), error(nil) + if err != nil { + return fmt.Errorf("field Extra: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementManifest. +// It implements [stackitem.Convertible] interface. +func (res *ManagementManifest) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 8) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementGroup) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Groups) + if err != nil { + return nil, fmt.Errorf("field Groups: %w", err) + } + items = append(items, itm) + + itm, err = func(in map[string]string) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var m = stackitem.NewMap() + for k, v := range in { + iKey, err := stackitem.NewByteArray([]byte(k)), error(nil) + if err != nil { + return nil, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := stackitem.NewByteArray([]byte(v)), error(nil) + if err != nil { + return nil, fmt.Errorf("key %v, wrong value: %w", k, err) + } + m.Add(iKey, iVal) + } + return m, nil + }(res.Features) + if err != nil { + return nil, fmt.Errorf("field Features: %w", err) + } + items = append(items, itm) + + itm, err = func(in []string) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray([]byte(v)), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.SupportedStandards) + if err != nil { + return nil, fmt.Errorf("field SupportedStandards: %w", err) + } + items = append(items, itm) + + itm, err = res.ABI.ToStackItem() + if err != nil { + return nil, fmt.Errorf("field ABI: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementPermission) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Permissions) + if err != nil { + return nil, fmt.Errorf("field Permissions: %w", err) + } + items = append(items, itm) + + itm, err = func(in []util.Uint160) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Trusts) + if err != nil { + return nil, fmt.Errorf("field Trusts: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.TryMake(res.Extra) + if err != nil { + return nil, fmt.Errorf("field Extra: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementManifest. +// It implements [smartcontract.Convertible] interface so that ManagementManifest +// could be used with invokers. +func (res *ManagementManifest) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 8) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementGroup) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Groups) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Groups: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in map[string]string) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.ParameterPair, 0, len(in)) + for k, v := range in { + iKey, err := smartcontract.NewParameterFromValue(k) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v, wrong value: %w", k, err) + } + prms = append(prms, smartcontract.ParameterPair{Key: iKey, Value: iVal}) + } + return smartcontract.Parameter{Type: smartcontract.MapType, Value: prms}, nil + }(res.Features) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Features: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []string) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.SupportedStandards) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field SupportedStandards: %w", err) + } + prms = append(prms, prm) + + prm, err = res.ABI.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ABI: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementPermission) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Permissions) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Permissions: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []util.Uint160) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Trusts) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Trusts: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Extra) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Extra: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementMethod converts stack item into *ManagementMethod. +// NULL item is returned as nil pointer without error. +func itemToManagementMethod(item stackitem.Item, err error) (*ManagementMethod, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementMethod) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementMethod is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementMethod{}) + +// Ensure *ManagementMethod is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementMethod{}) + +// FromStackItem retrieves fields of ManagementMethod from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementMethod) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Params, err = func(item stackitem.Item) ([]*ManagementParameter, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*ManagementParameter, len(arr)) + for i := range res { + res[i], err = itemToManagementParameter(arr[i], nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Params: %w", err) + } + + index++ + res.ReturnType, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ReturnType: %w", err) + } + + index++ + res.Offset, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Offset: %w", err) + } + + index++ + res.Safe, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field Safe: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementMethod. +// It implements [stackitem.Convertible] interface. +func (res *ManagementMethod) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 5) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = func(in []*ManagementParameter) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := v.ToStackItem() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Params) + if err != nil { + return nil, fmt.Errorf("field Params: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.ReturnType), error(nil) + if err != nil { + return nil, fmt.Errorf("field ReturnType: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Offset), error(nil) + if err != nil { + return nil, fmt.Errorf("field Offset: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewBool(res.Safe), error(nil) + if err != nil { + return nil, fmt.Errorf("field Safe: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementMethod. +// It implements [smartcontract.Convertible] interface so that ManagementMethod +// could be used with invokers. +func (res *ManagementMethod) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 5) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []*ManagementParameter) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := v.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Params) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Params: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.ReturnType) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ReturnType: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Offset) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Offset: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Safe) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Safe: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementParameter converts stack item into *ManagementParameter. +// NULL item is returned as nil pointer without error. +func itemToManagementParameter(item stackitem.Item, err error) (*ManagementParameter, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementParameter) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementParameter is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementParameter{}) + +// Ensure *ManagementParameter is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementParameter{}) + +// FromStackItem retrieves fields of ManagementParameter from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementParameter) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + res.Type, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Type: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementParameter. +// It implements [stackitem.Convertible] interface. +func (res *ManagementParameter) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray([]byte(res.Name)), error(nil) + if err != nil { + return nil, fmt.Errorf("field Name: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Type), error(nil) + if err != nil { + return nil, fmt.Errorf("field Type: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementParameter. +// It implements [smartcontract.Convertible] interface so that ManagementParameter +// could be used with invokers. +func (res *ManagementParameter) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Name) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Name: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Type) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Type: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToManagementPermission converts stack item into *ManagementPermission. +// NULL item is returned as nil pointer without error. +func itemToManagementPermission(item stackitem.Item, err error) (*ManagementPermission, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(ManagementPermission) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *ManagementPermission is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&ManagementPermission{}) + +// Ensure *ManagementPermission is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&ManagementPermission{}) + +// FromStackItem retrieves fields of ManagementPermission from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *ManagementPermission) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Contract, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Contract: %w", err) + } + + index++ + res.Methods, err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Methods: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing ManagementPermission. +// It implements [stackitem.Convertible] interface. +func (res *ManagementPermission) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = stackitem.NewByteArray(res.Contract.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field Contract: %w", err) + } + items = append(items, itm) + + itm, err = func(in []string) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray([]byte(v)), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.Methods) + if err != nil { + return nil, fmt.Errorf("field Methods: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing ManagementPermission. +// It implements [smartcontract.Convertible] interface so that ManagementPermission +// could be used with invokers. +func (res *ManagementPermission) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.Contract) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Contract: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []string) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.Methods) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Methods: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToStructsInternal converts stack item into *StructsInternal. +// NULL item is returned as nil pointer without error. +func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(StructsInternal) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *StructsInternal is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&StructsInternal{}) + +// Ensure *StructsInternal is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&StructsInternal{}) + +// FromStackItem retrieves fields of StructsInternal from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *StructsInternal) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 14 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Bool, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field Bool: %w", err) + } + + index++ + res.Int, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Int: %w", err) + } + + index++ + res.Bytes, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Bytes: %w", err) + } + + index++ + res.String, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field String: %w", err) + } + + index++ + res.H160, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field H160: %w", err) + } + + index++ + res.H256, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field H256: %w", err) + } + + index++ + res.PK, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PK: %w", err) + } + + index++ + res.PubKey, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field PubKey: %w", err) + } + + index++ + res.Sign, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Sign: %w", err) + } + + index++ + res.ArrOfBytes, err = func(item stackitem.Item) ([][]byte, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]byte, len(arr)) + for i := range res { + res[i], err = arr[i].TryBytes() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ArrOfBytes: %w", err) + } + + index++ + res.ArrOfH160, err = func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ArrOfH160: %w", err) + } + + index++ + res.Map, err = func(item stackitem.Item) (map[*big.Int]keys.PublicKeys, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int]keys.PublicKeys) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (keys.PublicKeys, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make(keys.PublicKeys, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Map: %w", err) + } + + index++ + res.Struct, err = itemToStructsInternal(arr[index], nil) + if err != nil { + return fmt.Errorf("field Struct: %w", err) + } + + index++ + res.UnexportedField, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field UnexportedField: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing StructsInternal. +// It implements [stackitem.Convertible] interface. +func (res *StructsInternal) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 14) + ) + itm, err = stackitem.NewBool(res.Bool), error(nil) + if err != nil { + return nil, fmt.Errorf("field Bool: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.Int), error(nil) + if err != nil { + return nil, fmt.Errorf("field Int: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Bytes), error(nil) + if err != nil { + return nil, fmt.Errorf("field Bytes: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray([]byte(res.String)), error(nil) + if err != nil { + return nil, fmt.Errorf("field String: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.H160.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field H160: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.H256.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("field H256: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.PK.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PK: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.PubKey.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("field PubKey: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewByteArray(res.Sign), error(nil) + if err != nil { + return nil, fmt.Errorf("field Sign: %w", err) + } + items = append(items, itm) + + itm, err = func(in [][]byte) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.ArrOfBytes) + if err != nil { + return nil, fmt.Errorf("field ArrOfBytes: %w", err) + } + items = append(items, itm) + + itm, err = func(in []util.Uint160) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v.BytesBE()), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(res.ArrOfH160) + if err != nil { + return nil, fmt.Errorf("field ArrOfH160: %w", err) + } + items = append(items, itm) + + itm, err = func(in map[*big.Int]keys.PublicKeys) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var m = stackitem.NewMap() + for k, v := range in { + iKey, err := (*stackitem.BigInteger)(k), error(nil) + if err != nil { + return nil, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := func(in keys.PublicKeys) (stackitem.Item, error) { + if in == nil { + return stackitem.Null{}, nil + } + + var items = make([]stackitem.Item, 0, len(in)) + for i, v := range in { + itm, err := stackitem.NewByteArray(v.Bytes()), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + items = append(items, itm) + } + return stackitem.NewArray(items), nil + }(v) + if err != nil { + return nil, fmt.Errorf("key %v, wrong value: %w", k, err) + } + m.Add(iKey, iVal) + } + return m, nil + }(res.Map) + if err != nil { + return nil, fmt.Errorf("field Map: %w", err) + } + items = append(items, itm) + + itm, err = res.Struct.ToStackItem() + if err != nil { + return nil, fmt.Errorf("field Struct: %w", err) + } + items = append(items, itm) + + itm, err = (*stackitem.BigInteger)(res.UnexportedField), error(nil) + if err != nil { + return nil, fmt.Errorf("field UnexportedField: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing StructsInternal. +// It implements [smartcontract.Convertible] interface so that StructsInternal +// could be used with invokers. +func (res *StructsInternal) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 14) + ) + prm, err = smartcontract.NewParameterFromValue(res.Bool) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Bool: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Int) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Int: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Bytes) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Bytes: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.String) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field String: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.H160) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field H160: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.H256) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field H256: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PK) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PK: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.PubKey) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field PubKey: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.Sign) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Sign: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in [][]byte) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.ArrOfBytes) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ArrOfBytes: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in []util.Uint160) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(res.ArrOfH160) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field ArrOfH160: %w", err) + } + prms = append(prms, prm) + + prm, err = func(in map[*big.Int]keys.PublicKeys) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.ParameterPair, 0, len(in)) + for k, v := range in { + iKey, err := smartcontract.NewParameterFromValue(k) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v: %w", k, err) + } + iVal, err := func(in keys.PublicKeys) (smartcontract.Parameter, error) { + if in == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var prms = make([]smartcontract.Parameter, 0, len(in)) + for i, v := range in { + prm, err := smartcontract.NewParameterFromValue(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("item %d: %w", i, err) + } + prms = append(prms, prm) + } + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil + }(v) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("key %v, wrong value: %w", k, err) + } + prms = append(prms, smartcontract.ParameterPair{Key: iKey, Value: iVal}) + } + return smartcontract.Parameter{Type: smartcontract.MapType, Value: prms}, nil + }(res.Map) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Map: %w", err) + } + prms = append(prms, prm) + + prm, err = res.Struct.ToSCParameter() + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field Struct: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.UnexportedField) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field UnexportedField: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} diff --git a/cli/smartcontract/rpcbindings/types/rpcbindings/dynamic_hash/rpcbindings_test.go b/cli/smartcontract/rpcbindings/types/rpcbindings/dynamic_hash/rpcbindings_test.go new file mode 100755 index 0000000..cab0cb5 --- /dev/null +++ b/cli/smartcontract/rpcbindings/types/rpcbindings/dynamic_hash/rpcbindings_test.go @@ -0,0 +1,563 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package types contains RPC wrappers for Types contract. +package types + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Unnamed is a contract-specific unnamed type used by its methods. +type Unnamed struct { + I *big.Int +} + +// UnnamedX is a contract-specific unnamedX type used by its methods. +type UnnamedX struct { + I *big.Int + B bool +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker. +func NewReader(invoker Invoker, hash util.Uint160) *ContractReader { + return &ContractReader{invoker, hash} +} + +// AAAStrings invokes `aAAStrings` method of contract. +func (c *ContractReader) AAAStrings(s [][][]string) ([][][]string, error) { + return func(item stackitem.Item, err error) ([][][]string, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) ([][][]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][][]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) ([][]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "aAAStrings", s))) +} + +// Any invokes `any` method of contract. +func (c *ContractReader) Any(a any) (any, error) { + return func(item stackitem.Item, err error) (any, error) { + if err != nil { + return nil, err + } + return item.Value(), error(nil) + }(unwrap.Item(c.invoker.Call(c.hash, "any", a))) +} + +// AnyMaps invokes `anyMaps` method of contract. +func (c *ContractReader) AnyMaps(m map[*big.Int]any) (map[*big.Int]any, error) { + return func(item stackitem.Item, err error) (map[*big.Int]any, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) (map[*big.Int]any, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int]any) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := m[i].Value.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "anyMaps", m))) +} + +// Bool invokes `bool` method of contract. +func (c *ContractReader) Bool(b bool) (bool, error) { + return unwrap.Bool(c.invoker.Call(c.hash, "bool", b)) +} + +// Bools invokes `bools` method of contract. +func (c *ContractReader) Bools(b []bool) ([]bool, error) { + return unwrap.ArrayOfBools(c.invoker.Call(c.hash, "bools", b)) +} + +// Bytes invokes `bytes` method of contract. +func (c *ContractReader) Bytes(b []byte) ([]byte, error) { + return unwrap.Bytes(c.invoker.Call(c.hash, "bytes", b)) +} + +// Bytess invokes `bytess` method of contract. +func (c *ContractReader) Bytess(b [][]byte) ([][]byte, error) { + return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "bytess", b)) +} + +// CrazyMaps invokes `crazyMaps` method of contract. +func (c *ContractReader) CrazyMaps(m map[*big.Int][]map[string][]util.Uint160) (map[*big.Int][]map[string][]util.Uint160, error) { + return func(item stackitem.Item, err error) (map[*big.Int][]map[string][]util.Uint160, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) (map[*big.Int][]map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int][]map[string][]util.Uint160) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]map[string][]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]map[string][]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string][]util.Uint160) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "crazyMaps", m))) +} + +// Hash160 invokes `hash160` method of contract. +func (c *ContractReader) Hash160(h util.Uint160) (util.Uint160, error) { + return unwrap.Uint160(c.invoker.Call(c.hash, "hash160", h)) +} + +// Hash160s invokes `hash160s` method of contract. +func (c *ContractReader) Hash160s(h []util.Uint160) ([]util.Uint160, error) { + return unwrap.ArrayOfUint160(c.invoker.Call(c.hash, "hash160s", h)) +} + +// Hash256 invokes `hash256` method of contract. +func (c *ContractReader) Hash256(h util.Uint256) (util.Uint256, error) { + return unwrap.Uint256(c.invoker.Call(c.hash, "hash256", h)) +} + +// Hash256s invokes `hash256s` method of contract. +func (c *ContractReader) Hash256s(h []util.Uint256) ([]util.Uint256, error) { + return unwrap.ArrayOfUint256(c.invoker.Call(c.hash, "hash256s", h)) +} + +// Int invokes `int` method of contract. +func (c *ContractReader) Int(i *big.Int) (*big.Int, error) { + return unwrap.BigInt(c.invoker.Call(c.hash, "int", i)) +} + +// Ints invokes `ints` method of contract. +func (c *ContractReader) Ints(i []*big.Int) ([]*big.Int, error) { + return unwrap.ArrayOfBigInts(c.invoker.Call(c.hash, "ints", i)) +} + +// Maps invokes `maps` method of contract. +func (c *ContractReader) Maps(m map[string]string) (map[string]string, error) { + return func(item stackitem.Item, err error) (map[string]string, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) (map[string]string, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string]string) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "maps", m))) +} + +// PublicKey invokes `publicKey` method of contract. +func (c *ContractReader) PublicKey(k *keys.PublicKey) (*keys.PublicKey, error) { + return unwrap.PublicKey(c.invoker.Call(c.hash, "publicKey", k)) +} + +// PublicKeys invokes `publicKeys` method of contract. +func (c *ContractReader) PublicKeys(k keys.PublicKeys) (keys.PublicKeys, error) { + return unwrap.ArrayOfPublicKeys(c.invoker.Call(c.hash, "publicKeys", k)) +} + +// Signature invokes `signature` method of contract. +func (c *ContractReader) Signature(s []byte) ([]byte, error) { + return unwrap.Bytes(c.invoker.Call(c.hash, "signature", s)) +} + +// Signatures invokes `signatures` method of contract. +func (c *ContractReader) Signatures(s [][]byte) ([][]byte, error) { + return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "signatures", s)) +} + +// String invokes `string` method of contract. +func (c *ContractReader) String(s string) (string, error) { + return unwrap.UTF8String(c.invoker.Call(c.hash, "string", s)) +} + +// Strings invokes `strings` method of contract. +func (c *ContractReader) Strings(s []string) ([]string, error) { + return unwrap.ArrayOfUTF8Strings(c.invoker.Call(c.hash, "strings", s)) +} + +// UnnamedStructs invokes `unnamedStructs` method of contract. +func (c *ContractReader) UnnamedStructs() (*Unnamed, error) { + return itemToUnnamed(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructs"))) +} + +// UnnamedStructsX invokes `unnamedStructsX` method of contract. +func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) { + return itemToUnnamedX(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructsX"))) +} + +// itemToUnnamed converts stack item into *Unnamed. +// NULL item is returned as nil pointer without error. +func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(Unnamed) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *Unnamed is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&Unnamed{}) + +// Ensure *Unnamed is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&Unnamed{}) + +// FromStackItem retrieves fields of Unnamed from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *Unnamed) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing Unnamed. +// It implements [stackitem.Convertible] interface. +func (res *Unnamed) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 1) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing Unnamed. +// It implements [smartcontract.Convertible] interface so that Unnamed +// could be used with invokers. +func (res *Unnamed) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 1) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToUnnamedX converts stack item into *UnnamedX. +// NULL item is returned as nil pointer without error. +func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(UnnamedX) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *UnnamedX is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&UnnamedX{}) + +// Ensure *UnnamedX is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&UnnamedX{}) + +// FromStackItem retrieves fields of UnnamedX from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *UnnamedX) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing UnnamedX. +// It implements [stackitem.Convertible] interface. +func (res *UnnamedX) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewBool(res.B), error(nil) + if err != nil { + return nil, fmt.Errorf("field B: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing UnnamedX. +// It implements [smartcontract.Convertible] interface so that UnnamedX +// could be used with invokers. +func (res *UnnamedX) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.B) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} diff --git a/cli/smartcontract/rpcbindings/types/rpcbindings/rpcbindings_test.go b/cli/smartcontract/rpcbindings/types/rpcbindings/rpcbindings_test.go new file mode 100644 index 0000000..8db5528 --- /dev/null +++ b/cli/smartcontract/rpcbindings/types/rpcbindings/rpcbindings_test.go @@ -0,0 +1,567 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package types contains RPC wrappers for Types contract. +package types + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// Unnamed is a contract-specific unnamed type used by its methods. +type Unnamed struct { + I *big.Int +} + +// UnnamedX is a contract-specific unnamedX type used by its methods. +type UnnamedX struct { + I *big.Int + B bool +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{invoker, hash} +} + +// AAAStrings invokes `aAAStrings` method of contract. +func (c *ContractReader) AAAStrings(s [][][]string) ([][][]string, error) { + return func(item stackitem.Item, err error) ([][][]string, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) ([][][]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][][]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) ([][]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "aAAStrings", s))) +} + +// Any invokes `any` method of contract. +func (c *ContractReader) Any(a any) (any, error) { + return func(item stackitem.Item, err error) (any, error) { + if err != nil { + return nil, err + } + return item.Value(), error(nil) + }(unwrap.Item(c.invoker.Call(c.hash, "any", a))) +} + +// AnyMaps invokes `anyMaps` method of contract. +func (c *ContractReader) AnyMaps(m map[*big.Int]any) (map[*big.Int]any, error) { + return func(item stackitem.Item, err error) (map[*big.Int]any, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) (map[*big.Int]any, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int]any) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := m[i].Value.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "anyMaps", m))) +} + +// Bool invokes `bool` method of contract. +func (c *ContractReader) Bool(b bool) (bool, error) { + return unwrap.Bool(c.invoker.Call(c.hash, "bool", b)) +} + +// Bools invokes `bools` method of contract. +func (c *ContractReader) Bools(b []bool) ([]bool, error) { + return unwrap.ArrayOfBools(c.invoker.Call(c.hash, "bools", b)) +} + +// Bytes invokes `bytes` method of contract. +func (c *ContractReader) Bytes(b []byte) ([]byte, error) { + return unwrap.Bytes(c.invoker.Call(c.hash, "bytes", b)) +} + +// Bytess invokes `bytess` method of contract. +func (c *ContractReader) Bytess(b [][]byte) ([][]byte, error) { + return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "bytess", b)) +} + +// CrazyMaps invokes `crazyMaps` method of contract. +func (c *ContractReader) CrazyMaps(m map[*big.Int][]map[string][]util.Uint160) (map[*big.Int][]map[string][]util.Uint160, error) { + return func(item stackitem.Item, err error) (map[*big.Int][]map[string][]util.Uint160, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) (map[*big.Int][]map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int][]map[string][]util.Uint160) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]map[string][]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]map[string][]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string][]util.Uint160) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "crazyMaps", m))) +} + +// Hash160 invokes `hash160` method of contract. +func (c *ContractReader) Hash160(h util.Uint160) (util.Uint160, error) { + return unwrap.Uint160(c.invoker.Call(c.hash, "hash160", h)) +} + +// Hash160s invokes `hash160s` method of contract. +func (c *ContractReader) Hash160s(h []util.Uint160) ([]util.Uint160, error) { + return unwrap.ArrayOfUint160(c.invoker.Call(c.hash, "hash160s", h)) +} + +// Hash256 invokes `hash256` method of contract. +func (c *ContractReader) Hash256(h util.Uint256) (util.Uint256, error) { + return unwrap.Uint256(c.invoker.Call(c.hash, "hash256", h)) +} + +// Hash256s invokes `hash256s` method of contract. +func (c *ContractReader) Hash256s(h []util.Uint256) ([]util.Uint256, error) { + return unwrap.ArrayOfUint256(c.invoker.Call(c.hash, "hash256s", h)) +} + +// Int invokes `int` method of contract. +func (c *ContractReader) Int(i *big.Int) (*big.Int, error) { + return unwrap.BigInt(c.invoker.Call(c.hash, "int", i)) +} + +// Ints invokes `ints` method of contract. +func (c *ContractReader) Ints(i []*big.Int) ([]*big.Int, error) { + return unwrap.ArrayOfBigInts(c.invoker.Call(c.hash, "ints", i)) +} + +// Maps invokes `maps` method of contract. +func (c *ContractReader) Maps(m map[string]string) (map[string]string, error) { + return func(item stackitem.Item, err error) (map[string]string, error) { + if err != nil { + return nil, err + } + return func(item stackitem.Item) (map[string]string, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string]string) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(item) + }(unwrap.Item(c.invoker.Call(c.hash, "maps", m))) +} + +// PublicKey invokes `publicKey` method of contract. +func (c *ContractReader) PublicKey(k *keys.PublicKey) (*keys.PublicKey, error) { + return unwrap.PublicKey(c.invoker.Call(c.hash, "publicKey", k)) +} + +// PublicKeys invokes `publicKeys` method of contract. +func (c *ContractReader) PublicKeys(k keys.PublicKeys) (keys.PublicKeys, error) { + return unwrap.ArrayOfPublicKeys(c.invoker.Call(c.hash, "publicKeys", k)) +} + +// Signature invokes `signature` method of contract. +func (c *ContractReader) Signature(s []byte) ([]byte, error) { + return unwrap.Bytes(c.invoker.Call(c.hash, "signature", s)) +} + +// Signatures invokes `signatures` method of contract. +func (c *ContractReader) Signatures(s [][]byte) ([][]byte, error) { + return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "signatures", s)) +} + +// String invokes `string` method of contract. +func (c *ContractReader) String(s string) (string, error) { + return unwrap.UTF8String(c.invoker.Call(c.hash, "string", s)) +} + +// Strings invokes `strings` method of contract. +func (c *ContractReader) Strings(s []string) ([]string, error) { + return unwrap.ArrayOfUTF8Strings(c.invoker.Call(c.hash, "strings", s)) +} + +// UnnamedStructs invokes `unnamedStructs` method of contract. +func (c *ContractReader) UnnamedStructs() (*Unnamed, error) { + return itemToUnnamed(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructs"))) +} + +// UnnamedStructsX invokes `unnamedStructsX` method of contract. +func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) { + return itemToUnnamedX(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructsX"))) +} + +// itemToUnnamed converts stack item into *Unnamed. +// NULL item is returned as nil pointer without error. +func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(Unnamed) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *Unnamed is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&Unnamed{}) + +// Ensure *Unnamed is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&Unnamed{}) + +// FromStackItem retrieves fields of Unnamed from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *Unnamed) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing Unnamed. +// It implements [stackitem.Convertible] interface. +func (res *Unnamed) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 1) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing Unnamed. +// It implements [smartcontract.Convertible] interface so that Unnamed +// could be used with invokers. +func (res *Unnamed) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 1) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} + +// itemToUnnamedX converts stack item into *UnnamedX. +// NULL item is returned as nil pointer without error. +func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(UnnamedX) + err = res.FromStackItem(item) + return res, err +} + +// Ensure *UnnamedX is a proper [stackitem.Convertible]. +var _ = stackitem.Convertible(&UnnamedX{}) + +// Ensure *UnnamedX is a proper [smartcontract.Convertible]. +var _ = smartcontract.Convertible(&UnnamedX{}) + +// FromStackItem retrieves fields of UnnamedX from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +// It implements [stackitem.Convertible] interface. +func (res *UnnamedX) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} + +// ToStackItem creates [stackitem.Item] representing UnnamedX. +// It implements [stackitem.Convertible] interface. +func (res *UnnamedX) ToStackItem() (stackitem.Item, error) { + if res == nil { + return stackitem.Null{}, nil + } + + var ( + err error + itm stackitem.Item + items = make([]stackitem.Item, 0, 2) + ) + itm, err = (*stackitem.BigInteger)(res.I), error(nil) + if err != nil { + return nil, fmt.Errorf("field I: %w", err) + } + items = append(items, itm) + + itm, err = stackitem.NewBool(res.B), error(nil) + if err != nil { + return nil, fmt.Errorf("field B: %w", err) + } + items = append(items, itm) + + return stackitem.NewStruct(items), nil +} + +// ToSCParameter creates [smartcontract.Parameter] representing UnnamedX. +// It implements [smartcontract.Convertible] interface so that UnnamedX +// could be used with invokers. +func (res *UnnamedX) ToSCParameter() (smartcontract.Parameter, error) { + if res == nil { + return smartcontract.Parameter{Type: smartcontract.AnyType}, nil + } + + var ( + err error + prm smartcontract.Parameter + prms = make([]smartcontract.Parameter, 0, 2) + ) + prm, err = smartcontract.NewParameterFromValue(res.I) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err) + } + prms = append(prms, prm) + + prm, err = smartcontract.NewParameterFromValue(res.B) + if err != nil { + return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err) + } + prms = append(prms, prm) + + return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil +} diff --git a/cli/smartcontract/rpcbindings/verify/rpcbindings_test.go b/cli/smartcontract/rpcbindings/verify/rpcbindings_test.go new file mode 100644 index 0000000..59e82a5 --- /dev/null +++ b/cli/smartcontract/rpcbindings/verify/rpcbindings_test.go @@ -0,0 +1,147 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package verify contains RPC wrappers for verify contract. +package verify + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// HelloWorldEvent represents "Hello world!" event emitted by the contract. +type HelloWorldEvent struct { + Args []any +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor + hash util.Uint160 +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + return &Contract{actor, hash} +} + +func (c *Contract) scriptForVerify() ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(c.hash, "verify") +} + +// Verify creates a transaction invoking `verify` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Verify() (util.Uint256, uint32, error) { + script, err := c.scriptForVerify() + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script) +} + +// VerifyTransaction creates a transaction invoking `verify` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) VerifyTransaction() (*transaction.Transaction, error) { + script, err := c.scriptForVerify() + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) +} + +// VerifyUnsigned creates a transaction invoking `verify` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) { + script, err := c.scriptForVerify() + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil) +} + +// HelloWorldEventsFromApplicationLog retrieves a set of all emitted events +// with "Hello world!" name from the provided [result.ApplicationLog]. +func HelloWorldEventsFromApplicationLog(log *result.ApplicationLog) ([]*HelloWorldEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*HelloWorldEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "Hello world!" { + continue + } + event := new(HelloWorldEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize HelloWorldEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to HelloWorldEvent or +// returns an error if it's not possible to do to so. +func (e *HelloWorldEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.Args, err = func(item stackitem.Item) ([]any, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]any, len(arr)) + for i := range res { + res[i], err = arr[i].Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Args: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go new file mode 100644 index 0000000..605b37a --- /dev/null +++ b/cli/smartcontract/smart_contract.go @@ -0,0 +1,993 @@ +package smartcontract + +import ( + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/cli/txctx" + "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/urfave/cli/v2" + "gopkg.in/yaml.v3" +) + +// addressFlagName and addressFlagAlias are a flag name and its alias +// used for address-related operations. It should be the same within +// the smartcontract package, thus, use this constant. +const ( + addressFlagName = "address" + addressFlagAlias = "a" +) + +var ( + errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag") + errNoMethod = errors.New("no method specified for function invocation command") + errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") + errFileExist = errors.New("file with given smart-contract name already exists") + addressFlag = &flags.AddressFlag{ + Name: addressFlagName, + Aliases: []string{addressFlagAlias}, + Usage: "Address to use as transaction signee (and gas source)", + } + inFlag = &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Usage: "Input file for the smart contract (*.nef)", + Action: cmdargs.EnsureNotEmpty("in"), + } + manifestFlag = &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Usage: "Manifest input file (*.manifest.json)", + Action: cmdargs.EnsureNotEmpty("manifest"), + } +) + +// ModVersion contains `pkg/interop` module version +// suitable to be used in go.mod. +var ModVersion string + +const ( + // smartContractTmpl is written to a file when used with `init` command. + // %s is parsed to be the smartContractName. + smartContractTmpl = `package %s + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +var notificationName string + +// init initializes notificationName before calling any other smart-contract method +func init() { + notificationName = "Hello world!" +} + +// RuntimeNotify sends runtime notification with "Hello world!" name +func RuntimeNotify(args []any) { + runtime.Notify(notificationName, args) +}` +) + +// NewCommands returns 'contract' command. +func NewCommands() []*cli.Command { + testInvokeScriptFlags := []cli.Flag{ + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input location of the .nef file that needs to be invoked", + Action: cmdargs.EnsureNotEmpty("in"), + }, + options.Historic, + } + testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...) + testInvokeFunctionFlags := []cli.Flag{options.Historic} + testInvokeFunctionFlags = append(testInvokeFunctionFlags, options.RPC...) + invokeFunctionFlags := []cli.Flag{ + addressFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.OutFlag, + txctx.ForceFlag, + txctx.AwaitFlag, + } + invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...) + invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) + deployFlags := append(invokeFunctionFlags, + cloneFlag(inFlag, true), + cloneFlag(manifestFlag, true), + ) + updateFlags := append([]cli.Flag{ + cloneFlag(inFlag, false), + cloneFlag(manifestFlag, false), + &cli.BoolFlag{ + Name: "strict", + Usage: "Fail immediately on NEP-22 nil-data call failure (skip 2-param fallback attempt)", + }, + }, invokeFunctionFlags...) + manifestAddGroupFlags := append([]cli.Flag{ + &flags.AddressFlag{ + Name: "sender", + Aliases: []string{"s"}, + Required: true, + Usage: "Deploy transaction sender", + }, + &flags.AddressFlag{ + Name: addressFlagName, // use the same name for handler code unification. + Aliases: []string{addressFlagAlias}, + Required: true, + Usage: "Account to sign group with", + }, + &cli.StringFlag{ + Name: "nef", + Aliases: []string{"n"}, + Required: true, + Usage: "Path to the NEF file", + Action: cmdargs.EnsureNotEmpty("nef"), + }, + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Path to the manifest", + Action: cmdargs.EnsureNotEmpty("manifest"), + }, + }, options.Wallet...) + return []*cli.Command{{ + Name: "contract", + Usage: "Compile - debug - deploy smart contracts", + Subcommands: []*cli.Command{ + { + Name: "compile", + Usage: "Compile a smart contract to a .nef file", + UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions] [--guess-eventtypes]", + Description: `Compiles given smart contract to a .nef file and emits other associated + information (manifest, bindings configuration, debug information files) if + asked to. If none of --out, --manifest, --config, --bindings flags are specified, + then the output filenames for these flags will be guessed using the contract + name or path provided via --in option by trimming/adding corresponding suffixes + to the common part of the path. In the latter case the configuration filepath + will be guessed from the --in option using the same rule. +`, + Action: contractCompile, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file for the smart contract to be compiled (*.go file or directory)", + Action: cmdargs.EnsureNotEmpty("in"), + }, + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, + Usage: "Output of the compiled contract", + }, + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Print out additional information after a compiling", + }, + &cli.StringFlag{ + Name: "debug", + Aliases: []string{"d"}, + Usage: "Emit debug info in a separate file", + }, + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Configuration input file (*.yml)", + }, + &cli.BoolFlag{ + Name: "no-standards", + Usage: "Do not check compliance with supported standards", + }, + &cli.BoolFlag{ + Name: "no-events", + Usage: "Do not check emitted events with the manifest", + }, + &cli.BoolFlag{ + Name: "no-permissions", + Usage: "Do not check if invoked contracts are allowed in manifest", + }, + &cli.BoolFlag{ + Name: "guess-eventtypes", + Usage: "Guess event types for smart-contract bindings configuration from the code usages", + }, + &cli.StringFlag{ + Name: "bindings", + Usage: "Output file for smart-contract bindings configuration", + }, + }, + }, + { + Name: "deploy", + Usage: "Deploy a smart contract (.nef with description)", + UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [data]", + Description: `Deploys given contract into the chain. The gas parameter is for additional + gas to be added as a network fee to prioritize the transaction. The data + parameter is an optional parameter to be passed to '_deploy' method. When + --await flag is specified, it waits for the transaction to be included + in a block. +`, + Action: contractDeploy, + Flags: deployFlags, + }, + { + Name: "update", + Usage: "Update deployed smart contract on the blockchain", + UsageText: "neo-go contract update -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [--strict] scripthash [data...] [--] [signers...]", + Description: `Updates deployed contract on the chain. The gas parameter is for additional + gas to be added as a network fee to prioritize the transaction. The data + parameter is an optional parameter to be passed to '_deploy' method. When + --await flag is specified, it waits for the transaction to be included + in a block. +`, + Action: contractUpdate, + Flags: updateFlags, + }, + { + Name: "destroy", + Usage: "Destroy deployed smart contract", + UsageText: "neo-go contract destroy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [--] [signers...]", + Description: `Destroys deployed contract. The gas parameter is for additional + gas to be added as a network fee to prioritize the transaction. When --await flag is + specified, it waits for the transaction to be included in a block. +`, + Action: contractDestroy, + Flags: invokeFunctionFlags, + }, + generateWrapperCmd, + generateRPCWrapperCmd, + { + Name: "invokefunction", + Usage: "Invoke deployed contract on the blockchain", + UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [method] [arguments...] [--] [signers...]", + Description: `Executes given (as a script hash) deployed script with the given method, + arguments and signers. Sender is included in the list of signers by default + with None witness scope. If you'd like to change default sender's scope, + specify it via signers parameter. See testinvokefunction documentation for + the details about parameters. It differs from testinvokefunction in that this + command sends an invocation transaction to the network. When --await flag is + specified, it waits for the transaction to be included in a block. +`, + Action: invokeFunction, + Flags: invokeFunctionFlags, + }, + { + Name: "testinvokefunction", + Usage: "Invoke deployed contract on the blockchain (test mode)", + UsageText: "neo-go contract testinvokefunction -r endpoint [--historic index/hash] scripthash [method] [arguments...] [--] [signers...]", + Description: `Executes given (as a script hash) deployed script with the given method, + arguments and signers (sender is not included by default). If no method is given + "" is passed to the script, if no arguments are given, an empty array is + passed, if no signers are given no array is passed. If signers are specified, + the first one of them is treated as a sender. All of the given arguments are + encapsulated into array before invoking the script. The script thus should + follow the regular convention of smart contract arguments (method string and + an array of other arguments). + +` + cmdargs.ParamsParsingDoc + ` + +` + cmdargs.SignersParsingDoc + ` +`, + Action: testInvokeFunction, + Flags: testInvokeFunctionFlags, + }, + { + Name: "testinvokescript", + Usage: "Invoke compiled AVM code in NEF format on the blockchain (test mode, not creating a transaction for it)", + UsageText: "neo-go contract testinvokescript -r endpoint -i input.nef [--historic index/hash] [signers...]", + Description: `Executes given compiled AVM instructions in NEF format with the given set of + signers not included sender by default. See testinvokefunction documentation + for the details about parameters. +`, + Action: testInvokeScript, + Flags: testInvokeScriptFlags, + }, + { + Name: "init", + Usage: "Initialize a new smart-contract in a directory with boiler plate code", + UsageText: "neo-go contract init -n name [--skip-details]", + Action: initSmartContract, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Required: true, + Usage: "Name of the smart-contract to be initialized", + Action: cmdargs.EnsureNotEmpty("name"), + }, + &cli.BoolFlag{ + Name: "skip-details", + Aliases: []string{"skip"}, + Usage: "Skip filling in the projects and contract details", + }, + }, + }, + { + Name: "inspect", + Usage: "Creates a user readable dump of the program instructions", + UsageText: "neo-go contract inspect -i file [-c]", + Action: inspect, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "compile", + Aliases: []string{"c"}, + Usage: "Compile input file (it should be go code then)", + }, + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file of the program (either .go or .nef)", + Action: cmdargs.EnsureNotEmpty("in"), + }, + }, + }, + { + Name: "calc-hash", + Usage: "Calculates hash of a contract after deployment", + UsageText: "neo-go contract calc-hash -i nef -m manifest -s address", + Action: calcHash, + Flags: []cli.Flag{ + &flags.AddressFlag{ + Name: "sender", + Aliases: []string{"s"}, + Required: true, + Usage: "Sender script hash or address", + }, + &cli.StringFlag{ + Name: "in", + Required: true, + Usage: "Path to NEF file", + Action: cmdargs.EnsureNotEmpty("in"), + }, + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Path to manifest file", + Action: cmdargs.EnsureNotEmpty("manifest"), + }, + }, + }, + { + Name: "manifest", + Usage: "Manifest-related commands", + Subcommands: []*cli.Command{ + { + Name: "add-group", + Usage: "Adds group to the manifest", + UsageText: "neo-go contract manifest add-group -w wallet [--wallet-config path] -n nef -m manifest -a address -s address", + Action: manifestAddGroup, + Flags: manifestAddGroupFlags, + }, + }, + }, + }, + }} +} + +// initSmartContract initializes a given directory with some boiler plate code. +func initSmartContract(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + contractName := ctx.String("name") + + // Check if the file already exists, if yes, exit + if _, err := os.Stat(contractName); err == nil { + return cli.Exit(errFileExist, 1) + } + + basePath := contractName + contractName = filepath.Base(contractName) + fileName := "main.go" + + // create base directory + if err := os.Mkdir(basePath, os.ModePerm); err != nil { + return cli.Exit(err, 1) + } + + m := ProjectConfig{ + Name: contractName, + SourceURL: "http://example.com/", + SupportedStandards: []string{}, + SafeMethods: []string{}, + Events: []compiler.HybridEvent{ + { + Name: "Hello world!", + Parameters: []compiler.HybridParameter{ + { + Parameter: manifest.Parameter{ + Name: "args", + Type: smartcontract.ArrayType, + }, + }, + }, + }, + }, + Permissions: []permission{permission(*manifest.NewPermission(manifest.PermissionWildcard))}, + } + b, err := yaml.Marshal(m) + if err != nil { + return cli.Exit(err, 1) + } + if err := os.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { + return cli.Exit(err, 1) + } + + ver := ModVersion + if ver == "" { + ver = "latest" + } + + gm := []byte("module " + contractName + ` + +go 1.24 + +require ( + github.com/nspcc-dev/neo-go/pkg/interop ` + ver + ` +)`) + if err := os.WriteFile(filepath.Join(basePath, "go.mod"), gm, 0644); err != nil { + return cli.Exit(err, 1) + } + + data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) + if err := os.WriteFile(filepath.Join(basePath, fileName), data, 0644); err != nil { + return cli.Exit(err, 1) + } + + fmt.Fprintf(ctx.App.Writer, "Successfully initialized smart contract [%s]\n", contractName) + + return nil +} + +func contractCompile(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + src := ctx.String("in") + manifestFile := ctx.String("manifest") + confFile := ctx.String("config") + debugFile := ctx.String("debug") + out := ctx.String("out") + bindings := ctx.String("bindings") + if len(confFile) == 0 && (len(manifestFile) != 0 || len(debugFile) != 0 || len(bindings) != 0) { + return cli.Exit(errNoConfFile, 1) + } + autocomplete := len(manifestFile) == 0 && + len(confFile) == 0 && + len(out) == 0 && + len(bindings) == 0 + if autocomplete { + var root string + fileInfo, err := os.Stat(src) + if err != nil { + return cli.Exit(fmt.Errorf("failed to stat source file or directory: %w", err), 1) + } + if fileInfo.IsDir() { + base := filepath.Base(fileInfo.Name()) + if base == string(filepath.Separator) { + base = "contract" + } + root = filepath.Join(src, base) + } else { + root = strings.TrimSuffix(src, ".go") + } + manifestFile = root + ".manifest.json" + confFile = root + ".yml" + out = root + ".nef" + bindings = root + ".bindings.yml" + } + + o := &compiler.Options{ + Outfile: out, + + DebugInfo: debugFile, + ManifestFile: manifestFile, + BindingsFile: bindings, + + NoStandardCheck: ctx.Bool("no-standards"), + NoEventsCheck: ctx.Bool("no-events"), + NoPermissionsCheck: ctx.Bool("no-permissions"), + + GuessEventTypes: ctx.Bool("guess-eventtypes"), + } + + if len(confFile) != 0 { + conf, err := ParseContractConfig(confFile) + if err != nil { + return err + } + o.Name = conf.Name + o.SourceURL = conf.SourceURL + o.ContractEvents = conf.Events + o.DeclaredNamedTypes = conf.NamedTypes + o.ContractSupportedStandards = conf.SupportedStandards + o.Permissions = make([]manifest.Permission, len(conf.Permissions)) + for i := range conf.Permissions { + o.Permissions[i] = manifest.Permission(conf.Permissions[i]) + } + o.SafeMethods = conf.SafeMethods + o.Overloads = conf.Overloads + } + + result, err := compiler.CompileAndSave(src, o) + if err != nil { + return cli.Exit(err, 1) + } + if ctx.Bool("verbose") { + fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result)) + } + + return nil +} + +func calcHash(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + sender := ctx.Generic("sender").(*flags.Address) + p := ctx.String("in") + mpath := ctx.String("manifest") + + f, err := os.ReadFile(p) + if err != nil { + return cli.Exit(fmt.Errorf("can't read .nef file: %w", err), 1) + } + nefFile, err := nef.FileFromBytes(f) + if err != nil { + return cli.Exit(fmt.Errorf("can't unmarshal .nef file: %w", err), 1) + } + manifestBytes, err := os.ReadFile(mpath) + if err != nil { + return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1) + } + m := &manifest.Manifest{} + err = json.Unmarshal(manifestBytes, m) + if err != nil { + return cli.Exit(fmt.Errorf("failed to restore manifest file: %w", err), 1) + } + fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(sender.Uint160(), nefFile.Checksum, m.Name).StringLE()) + return nil +} + +func testInvokeFunction(ctx *cli.Context) error { + return invokeInternal(ctx, false) +} + +func invokeFunction(ctx *cli.Context) error { + return invokeInternal(ctx, true) +} + +func invokeInternal(ctx *cli.Context, signAndPush bool) error { + var ( + err error + exitErr cli.ExitCoder + operation string + params []any + paramsStart = 1 + scParams []smartcontract.Parameter + cosigners []transaction.Signer + cosignersOffset = 0 + ) + + args := ctx.Args() + if !args.Present() { + return cli.Exit(errNoScriptHash, 1) + } + argsSlice := args.Slice() + script, err := flags.ParseAddress(argsSlice[0]) + if err != nil { + return cli.Exit(fmt.Errorf("incorrect script hash: %w", err), 1) + } + if len(argsSlice) <= 1 { + return cli.Exit(errNoMethod, 1) + } + operation = argsSlice[1] + paramsStart++ + + if len(argsSlice) > paramsStart { + cosignersOffset, scParams, err = cmdargs.ParseParams(argsSlice[paramsStart:], true) + if err != nil { + return cli.Exit(err, 1) + } + params = make([]any, len(scParams)) + for i := range scParams { + params[i] = scParams[i] + } + } + + cosignersStart := paramsStart + cosignersOffset + cosigners, exitErr = cmdargs.GetSignersFromContext(ctx, cosignersStart) + if exitErr != nil { + return exitErr + } + + var ( + acc *wallet.Account + w *wallet.Wallet + ) + if signAndPush { + acc, w, err = options.GetAccFromContext(ctx) + if err != nil { + return cli.Exit(err, 1) + } + defer w.Close() + } + + return invokeWithArgs(ctx, acc, w, script, operation, params, cosigners) +} + +func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []any, cosigners []transaction.Signer) error { + var ( + err error + signersAccounts []actor.SignerAccount + resp *result.Invoke + signAndPush = acc != nil + inv *invoker.Invoker + act *actor.Actor + ) + if signAndPush { + signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None) + if err != nil { + return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1) + } + } + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + if signAndPush { + _, act, err = options.GetRPCWithActor(gctx, ctx, signersAccounts) + if err != nil { + return err + } + inv = &act.Invoker + } else { + _, inv, err = options.GetRPCWithInvoker(gctx, ctx, cosigners) + if err != nil { + return err + } + } + out := ctx.String("out") + resp, err = inv.Call(script, operation, params...) + if err != nil { + return cli.Exit(err, 1) + } + if resp.State != "HALT" { + errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException) + if !signAndPush { + return cli.Exit(errText, 1) + } + + action := "send" + process := "Sending" + if out != "" { + action = "save" + process = "Saving" + } + if !ctx.Bool("force") { + return cli.Exit(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1) + } + fmt.Fprintln(ctx.App.Writer, errText+".\n"+process+" transaction...") + } + if !signAndPush { + b, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return cli.Exit(err, 1) + } + + fmt.Fprintln(ctx.App.Writer, string(b)) + return nil + } + if len(resp.Script) == 0 { + return cli.Exit(errors.New("no script returned from the RPC node"), 1) + } + tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed, nil) + if err != nil { + return cli.Exit(fmt.Errorf("failed to create tx: %w", err), 1) + } + return txctx.SignAndSend(ctx, act, acc, tx) +} + +func testInvokeScript(ctx *cli.Context) error { + src := ctx.String("in") + b, err := os.ReadFile(src) + if err != nil { + return cli.Exit(err, 1) + } + nefFile, err := nef.FileFromBytes(b) + if err != nil { + return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1) + } + + signers, exitErr := cmdargs.GetSignersFromContext(ctx, 0) + if exitErr != nil { + return exitErr + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + _, inv, err := options.GetRPCWithInvoker(gctx, ctx, signers) + if err != nil { + return err + } + + resp, err := inv.Run(nefFile.Script) + if err != nil { + return cli.Exit(err, 1) + } + + b, err = json.MarshalIndent(resp, "", " ") + if err != nil { + return cli.Exit(err, 1) + } + + fmt.Fprintln(ctx.App.Writer, string(b)) + + return nil +} + +// ProjectConfig contains project metadata. +type ProjectConfig struct { + Name string + SourceURL string + SafeMethods []string + SupportedStandards []string + Events []compiler.HybridEvent + Permissions []permission + Overloads map[string]string `yaml:"overloads,omitempty"` + NamedTypes map[string]binding.ExtendedType `yaml:"namedtypes,omitempty"` +} + +func inspect(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + in := ctx.String("in") + compile := ctx.Bool("compile") + var ( + b []byte + err error + ) + if compile { + b, err = compiler.Compile(in, nil) + if err != nil { + return cli.Exit(fmt.Errorf("failed to compile: %w", err), 1) + } + } else { + f, err := os.ReadFile(in) + if err != nil { + return cli.Exit(fmt.Errorf("failed to read .nef file: %w", err), 1) + } + nefFile, err := nef.FileFromBytes(f) + if err != nil { + return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1) + } + b = nefFile.Script + } + v := vm.New() + v.LoadScript(b) + v.PrintOps(ctx.App.Writer) + + return nil +} + +// contractDeploy deploys contract. +func contractDeploy(ctx *cli.Context) error { + nefFile, f, err := readNEFFile(ctx.String("in")) + if err != nil { + return cli.Exit(err, 1) + } + + m, manifestBytes, err := readManifest(ctx.String("manifest"), util.Uint160{}) + if err != nil { + return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1) + } + + var appCallParams = []any{f, manifestBytes} + + signOffset, data, err := cmdargs.ParseParams(ctx.Args().Slice(), true) + if err != nil { + return cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) + } + if len(data) > 1 { + return cli.Exit("'data' should be represented as a single parameter", 1) + } + if len(data) != 0 { + appCallParams = append(appCallParams, data[0]) + } + + acc, w, err := options.GetAccFromContext(ctx) + if err != nil { + return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1) + } + defer w.Close() + sender := acc.ScriptHash() + + cosigners, sgnErr := cmdargs.GetSignersFromContext(ctx, signOffset) + if sgnErr != nil { + return err + } else if len(cosigners) == 0 { + cosigners = []transaction.Signer{{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.CalledByEntry, + }} + } + + extErr := invokeWithArgs(ctx, acc, w, management.Hash, "deploy", appCallParams, cosigners) + if extErr != nil { + return extErr + } + + hash := state.CreateContractHash(sender, nefFile.Checksum, m.Name) + fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE()) + return nil +} + +// contractUpdate updates an existing smart contract. +func contractUpdate(ctx *cli.Context) error { + var ( + paramsStart = 0 + args []string + nefBytes []byte + manifestBytes []byte + nefParam any + manifestParam any + ) + args = ctx.Args().Slice() + if len(args) < 1 { + return cli.Exit(errNoScriptHash, 1) + } + contractHash, err := flags.ParseAddress(args[0]) + if err != nil { + return cli.Exit(fmt.Errorf("invalid contract hash '%s': %w", args[0], err), 1) + } + paramsStart++ + + if ctx.String("in") != "" { + _, nefBytes, err = readNEFFile(ctx.String("in")) + if err != nil { + return cli.Exit(fmt.Errorf("failed to read .nef file: %w", err), 1) + } + nefParam = nefBytes + } + if ctx.String("manifest") != "" { + _, manifestBytes, err = readManifest(ctx.String("manifest"), contractHash) + if err != nil { + return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1) + } + manifestParam = manifestBytes + } + if nefBytes == nil && manifestBytes == nil { + return cli.Exit(fmt.Errorf("either manifest or .nef required"), 1) + } + + params := []any{nefParam, manifestParam} + + signOffset, data, err := cmdargs.ParseParams(args[paramsStart:], true) + if err != nil { + return cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) + } + if len(data) > 1 { + return cli.Exit("'data' should be represented as a single parameter", 1) + } + + acc, w, err := options.GetAccFromContext(ctx) + if err != nil { + return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1) + } + defer w.Close() + + cosigners, exitErr := cmdargs.GetSignersFromContext(ctx, paramsStart+signOffset) + if exitErr != nil { + return exitErr + } + if len(cosigners) == 0 { + cosigners = []transaction.Signer{{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.CalledByEntry, + }} + } + if len(data) != 0 { + params = append(params, data[0]) + return invokeWithArgs(ctx, acc, w, contractHash, "update", params, cosigners) + } + + // First, try the NEP-22-style update (script, manifest, data) + // (or pass nil as “data” for NEP-22 compatibility). + params = append(params, nil) + err = invokeWithArgs(ctx, acc, w, contractHash, "update", params, cosigners) + // If that fails (and --strict isn’t set), fallback to the original format + // which expects only (script, manifest) with no extra data. + if err == nil || ctx.Bool("strict") || + !strings.Contains(err.Error(), "System.Contract.Call failed: method not found: update/3.") && + !strings.Contains(err.Error(), `Method "update" with 3 parameter(s) doesn't exist in the contract`) { + return err + } + return invokeWithArgs(ctx, acc, w, contractHash, "update", params[:2], cosigners) +} + +// contractDestroy destroys an existing smart contract. +func contractDestroy(ctx *cli.Context) error { + var ( + signOffset = 1 + paramsStart = 0 + args []string + ) + args = ctx.Args().Slice() + if len(args) < 1 { + return cli.Exit(errNoScriptHash, 1) + } + contractHash, err := flags.ParseAddress(args[0]) + if err != nil { + return cli.Exit(fmt.Errorf("invalid contract hash '%s': %w", args[0], err), 1) + } + paramsStart++ + + acc, w, err := options.GetAccFromContext(ctx) + if err != nil { + return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1) + } + defer w.Close() + + cosigners, exitErr := cmdargs.GetSignersFromContext(ctx, paramsStart+signOffset) + if exitErr != nil { + return exitErr + } + if len(cosigners) == 0 { + cosigners = []transaction.Signer{{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.CalledByEntry, + }} + } + + return invokeWithArgs(ctx, acc, w, contractHash, "destroy", nil, cosigners) +} + +// ParseContractConfig reads contract configuration file (.yaml) and returns unmarshalled ProjectConfig. +func ParseContractConfig(confFile string) (ProjectConfig, error) { + conf := ProjectConfig{} + confBytes, err := os.ReadFile(confFile) + if err != nil { + return conf, cli.Exit(err, 1) + } + + err = yaml.Unmarshal(confBytes, &conf) + if err != nil { + return conf, cli.Exit(fmt.Errorf("bad config: %w", err), 1) + } + return conf, nil +} + +func cloneFlag(p *cli.StringFlag, required bool) *cli.StringFlag { + f := *p + f.Required = required + return &f +} diff --git a/cli/smartcontract/smart_contract_test.go b/cli/smartcontract/smart_contract_test.go new file mode 100644 index 0000000..7bab336 --- /dev/null +++ b/cli/smartcontract/smart_contract_test.go @@ -0,0 +1,138 @@ +package smartcontract + +import ( + "flag" + "os" + "testing" + + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" + "gopkg.in/yaml.v3" +) + +func TestInitSmartContract(t *testing.T) { + d := t.TempDir() + testWD, err := os.Getwd() + require.NoError(t, err) + err = os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(testWD)) }) + contractName := "testContract" + + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("name", contractName, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + require.NoError(t, initSmartContract(ctx)) + dirInfo, err := os.Stat(contractName) + require.NoError(t, err) + require.True(t, dirInfo.IsDir()) + files, err := os.ReadDir(contractName) + require.NoError(t, err) + require.Equal(t, 3, len(files)) + require.Equal(t, "go.mod", files[0].Name()) + require.Equal(t, "main.go", files[1].Name()) + require.Equal(t, "neo-go.yml", files[2].Name()) + main, err := os.ReadFile(contractName + "/" + files[1].Name()) + require.NoError(t, err) + require.Equal(t, + `package `+contractName+` + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +var notificationName string + +// init initializes notificationName before calling any other smart-contract method +func init() { + notificationName = "Hello world!" +} + +// RuntimeNotify sends runtime notification with "Hello world!" name +func RuntimeNotify(args []any) { + runtime.Notify(notificationName, args) +}`, string(main)) + + manifest, err := os.ReadFile(contractName + "/" + files[2].Name()) + require.NoError(t, err) + expected := `name: testContract +sourceurl: http://example.com/ +safemethods: [] +supportedstandards: [] +events: + - name: Hello world! + parameters: + - name: args + type: Array +permissions: + - methods: '*' +` + require.Equal(t, expected, string(manifest)) +} + +func testPermissionMarshal(t *testing.T, p *manifest.Permission, expected string) { + out, err := yaml.Marshal((*permission)(p)) + require.NoError(t, err) + require.Equal(t, expected, string(out)) + + t.Run("Unmarshal", func(t *testing.T) { + actual := new(permission) + require.NoError(t, yaml.Unmarshal(out, actual)) + require.Equal(t, p, (*manifest.Permission)(actual)) + }) +} + +func TestPermissionMarshal(t *testing.T) { + t.Run("Wildcard", func(t *testing.T) { + p := manifest.NewPermission(manifest.PermissionWildcard) + testPermissionMarshal(t, p, "methods: '*'\n") + }) + t.Run("no allowed methods", func(t *testing.T) { + p := manifest.NewPermission(manifest.PermissionWildcard) + p.Methods.Restrict() + testPermissionMarshal(t, p, "methods: []\n") + }) + t.Run("hash", func(t *testing.T) { + h := random.Uint160() + p := manifest.NewPermission(manifest.PermissionHash, h) + testPermissionMarshal(t, p, + "hash: "+h.StringLE()+"\n"+ + "methods: '*'\n") + }) + t.Run("group with some methods", func(t *testing.T) { + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + + p := manifest.NewPermission(manifest.PermissionGroup, priv.PublicKey()) + p.Methods.Add("abc") + p.Methods.Add("lamao") + testPermissionMarshal(t, p, + "group: "+priv.PublicKey().StringCompressed()+"\n"+ + "methods:\n - abc\n - lamao\n") + }) +} + +func TestPermissionUnmarshalInvalid(t *testing.T) { + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + + pub := priv.PublicKey().StringCompressed() + u160 := random.Uint160().StringLE() + testCases := []string{ + "hash: []\nmethods: '*'\n", // invalid hash type + "hash: notahex\nmethods: '*'\n", // invalid hash + "group: []\nmethods: '*'\n", // invalid group type + "group: notahex\nmethods: '*'\n", // invalid group + "hash: " + u160 + "\n", // missing methods + "group: " + pub + "\nhash: " + u160 + "\nmethods: '*'", // hash/group conflict + "hash: " + u160 + "\nmethods:\n a: b\n", // invalid methods type + "hash: " + u160 + "\nmethods:\n- []\n", // methods array, invalid single + } + + for _, tc := range testCases { + t.Run(tc, func(t *testing.T) { + require.Error(t, yaml.Unmarshal([]byte(tc), new(permission))) + }) + } +} diff --git a/cli/smartcontract/testdata/deploy/main.go b/cli/smartcontract/testdata/deploy/main.go new file mode 100644 index 0000000..0017e0b --- /dev/null +++ b/cli/smartcontract/testdata/deploy/main.go @@ -0,0 +1,91 @@ +package deploy + +import ( + "github.com/nspcc-dev/neo-go/cli/smartcontract/testdata/deploy/sub" + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + "github.com/nspcc-dev/neo-go/pkg/interop/storage" +) + +var key = "key" + +const mgmtKey = "mgmt" + +func _deploy(data any, isUpdate bool) { + var value string + + ctx := storage.GetContext() + if isUpdate { + value = "on update" + } else { + value = "on create" + sh := runtime.GetCallingScriptHash() + storage.Put(ctx, mgmtKey, sh) + + if data != nil { + arr := data.([]any) + for i := 0; i < len(arr)-1; i += 2 { + storage.Put(ctx, arr[i], arr[i+1]) + } + } + } + + storage.Put(ctx, key, value) +} + +// Fail just fails. +func Fail() { + panic("as expected") +} + +// CheckSenderWitness checks sender's witness. +func CheckSenderWitness() { + tx := runtime.GetScriptContainer() + if !runtime.CheckWitness(tx.Sender) { + panic("not witnessed") + } +} + +// Update updates the contract with a new one. +func Update(script, manifest []byte, data any) { + ctx := storage.GetReadOnlyContext() + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "update", contract.All, script, manifest, data) +} + +// GetValue returns the stored value. +func GetValue() string { + ctx := storage.GetReadOnlyContext() + val1 := storage.Get(ctx, key) + val2 := storage.Get(ctx, sub.Key) + return val1.(string) + "|" + val2.(string) +} + +// GetValueWithKey returns the stored value with the specified key. +func GetValueWithKey(key string) string { + ctx := storage.GetReadOnlyContext() + return storage.Get(ctx, key).(string) +} + +// TestFind finds items with the specified prefix. +func TestFind(f storage.FindFlags) []any { + ctx := storage.GetContext() + storage.Put(ctx, "findkey1", "value1") + storage.Put(ctx, "findkey2", "value2") + + var result []any + iter := storage.Find(ctx, "findkey", f) + for iterator.Next(iter) { + result = append(result, iterator.Value(iter)) + } + return result +} + +// Destroy destroys the contract. +func Destroy() { + ctx := storage.GetReadOnlyContext() + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "destroy", contract.All) +} diff --git a/cli/smartcontract/testdata/deploy/neo-go.yml b/cli/smartcontract/testdata/deploy/neo-go.yml new file mode 100644 index 0000000..a50fc6e --- /dev/null +++ b/cli/smartcontract/testdata/deploy/neo-go.yml @@ -0,0 +1,4 @@ +name: Test deploy +permissions: + - hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd + methods: ["update", "destroy"] diff --git a/cli/smartcontract/testdata/deploy/sub/put.go b/cli/smartcontract/testdata/deploy/sub/put.go new file mode 100644 index 0000000..08f6347 --- /dev/null +++ b/cli/smartcontract/testdata/deploy/sub/put.go @@ -0,0 +1,14 @@ +package sub + +import "github.com/nspcc-dev/neo-go/pkg/interop/storage" + +var Key = "sub" + +func _deploy(data any, isUpdate bool) { + ctx := storage.GetContext() + value := "sub create" + if isUpdate { + value = "sub update" + } + storage.Put(ctx, Key, value) +} diff --git a/cli/smartcontract/testdata/deploy/update.manifest.json b/cli/smartcontract/testdata/deploy/update.manifest.json new file mode 100755 index 0000000..5fcf685 --- /dev/null +++ b/cli/smartcontract/testdata/deploy/update.manifest.json @@ -0,0 +1 @@ +{"name":"Test deploy","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":15,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"checkSenderWitness","offset":201,"parameters":[],"returntype":"Void","safe":false},{"name":"fail","offset":186,"parameters":[],"returntype":"Void","safe":false},{"name":"getValueUpdated","offset":294,"parameters":[],"returntype":"String","safe":false},{"name":"getValueWithKey","offset":343,"parameters":[{"name":"key","type":"String"}],"returntype":"String","safe":false},{"name":"testFind","offset":368,"parameters":[{"name":"f","type":"InteropInterface"}],"returntype":"Array","safe":false},{"name":"update","offset":238,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]}],"supportedstandards":[],"trusts":[],"extra":null} diff --git a/cli/smartcontract/testdata/deploy/updated.go b/cli/smartcontract/testdata/deploy/updated.go new file mode 100644 index 0000000..fb6681d --- /dev/null +++ b/cli/smartcontract/testdata/deploy/updated.go @@ -0,0 +1,6 @@ +package deploy + +// NewMethod in updated contract. +func NewMethod() int { + return 42 +} diff --git a/cli/smartcontract/testdata/gas/gas.go b/cli/smartcontract/testdata/gas/gas.go new file mode 100644 index 0000000..b5fb268 --- /dev/null +++ b/cli/smartcontract/testdata/gas/gas.go @@ -0,0 +1,52 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package gastoken contains RPC wrappers for GasToken contract. +package gastoken + +import ( + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0xcf, 0x76, 0xe2, 0x8b, 0xd0, 0x6, 0x2c, 0x4a, 0x47, 0x8e, 0xe3, 0x55, 0x61, 0x1, 0x13, 0x19, 0xf3, 0xcf, 0xa4, 0xd2} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep17.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep17.Actor +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep17.TokenReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep17.TokenWriter + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{*nep17.NewReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + var nep17t = nep17.New(actor, hash) + return &Contract{ContractReader{nep17t.TokenReader, actor, hash}, nep17t.TokenWriter, actor, hash} +} diff --git a/cli/smartcontract/testdata/gas/gas.manifest.json b/cli/smartcontract/testdata/gas/gas.manifest.json new file mode 100644 index 0000000..b2421f4 --- /dev/null +++ b/cli/smartcontract/testdata/gas/gas.manifest.json @@ -0,0 +1 @@ +{"name":"GasToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"symbol","parameters":[],"returntype":"String","offset":14,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":28,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null} diff --git a/cli/smartcontract/testdata/invalid1/invalid.go b/cli/smartcontract/testdata/invalid1/invalid.go new file mode 100644 index 0000000..3bdf220 --- /dev/null +++ b/cli/smartcontract/testdata/invalid1/invalid.go @@ -0,0 +1,21 @@ +// invalid is an example of a contract which doesn't pass event check. +package invalid1 + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +// Notify1 emits a correctly typed event. +func Notify1() bool { + runtime.Notify("Event", interop.Hash160{1, 2, 3}) + + return true +} + +// Notify2 emits an invalid event (ByteString instead of Hash160). +func Notify2() bool { + runtime.Notify("Event", []byte{1, 2, 3}) + + return true +} diff --git a/cli/smartcontract/testdata/invalid1/invalid.yml b/cli/smartcontract/testdata/invalid1/invalid.yml new file mode 100644 index 0000000..73839ca --- /dev/null +++ b/cli/smartcontract/testdata/invalid1/invalid.yml @@ -0,0 +1,7 @@ +name: "Invalid example" +supportedstandards: [] +events: + - name: Event + parameters: + - name: address + type: Hash160 diff --git a/cli/smartcontract/testdata/invalid2/invalid.go b/cli/smartcontract/testdata/invalid2/invalid.go new file mode 100644 index 0000000..07f1002 --- /dev/null +++ b/cli/smartcontract/testdata/invalid2/invalid.go @@ -0,0 +1,21 @@ +// invalid is an example of a contract which doesn't pass event check. +package invalid2 + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +// Notify1 emits a correctly typed event. +func Notify1() bool { + runtime.Notify("Event", interop.Hash160{1, 2, 3}) + + return true +} + +// Notify2 emits an invalid event (extra parameter). +func Notify2() bool { + runtime.Notify("Event", interop.Hash160{1, 2, 3}, "extra parameter") + + return true +} diff --git a/cli/smartcontract/testdata/invalid2/invalid.yml b/cli/smartcontract/testdata/invalid2/invalid.yml new file mode 100644 index 0000000..73839ca --- /dev/null +++ b/cli/smartcontract/testdata/invalid2/invalid.yml @@ -0,0 +1,7 @@ +name: "Invalid example" +supportedstandards: [] +events: + - name: Event + parameters: + - name: address + type: Hash160 diff --git a/cli/smartcontract/testdata/invalid3/invalid.go b/cli/smartcontract/testdata/invalid3/invalid.go new file mode 100644 index 0000000..2dc7c96 --- /dev/null +++ b/cli/smartcontract/testdata/invalid3/invalid.go @@ -0,0 +1,21 @@ +// invalid is an example of a contract which doesn't pass event check. +package invalid3 + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +// Notify1 emits a correctly typed event. +func Notify1() bool { + runtime.Notify("Event", interop.Hash160{1, 2, 3}) + + return true +} + +// Notify2 emits an invalid event (missing from manifest). +func Notify2() bool { + runtime.Notify("AnotherEvent", interop.Hash160{1, 2, 3}) + + return true +} diff --git a/cli/smartcontract/testdata/invalid3/invalid.yml b/cli/smartcontract/testdata/invalid3/invalid.yml new file mode 100644 index 0000000..73839ca --- /dev/null +++ b/cli/smartcontract/testdata/invalid3/invalid.yml @@ -0,0 +1,7 @@ +name: "Invalid example" +supportedstandards: [] +events: + - name: Event + parameters: + - name: address + type: Hash160 diff --git a/cli/smartcontract/testdata/invalid4/invalid.go b/cli/smartcontract/testdata/invalid4/invalid.go new file mode 100644 index 0000000..acb769b --- /dev/null +++ b/cli/smartcontract/testdata/invalid4/invalid.go @@ -0,0 +1,5 @@ +package invalid4 + +func Verify() bool { + return true +} diff --git a/cli/smartcontract/testdata/invalid4/invalid.yml b/cli/smartcontract/testdata/invalid4/invalid.yml new file mode 100644 index 0000000..636d914 --- /dev/null +++ b/cli/smartcontract/testdata/invalid4/invalid.yml @@ -0,0 +1,2 @@ +name: Test bad source url +sourceurl: http://example.com/with/some/huge/path/that/cant/be/ever/normally/reached/but/we/need/to/test/for/it/anyway/because/there/is/some/path/in/the/code/that/does/this/check/and/it/should/be/triggered/to/ensure/proper/coverage/in/this/particular/component/of/our/perfect/compiler diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go new file mode 100644 index 0000000..5205e4d --- /dev/null +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -0,0 +1,511 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nameservice contains RPC wrappers for NameService contract. +package nameservice + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0xde, 0x46, 0x5f, 0x5d, 0x50, 0x57, 0xcf, 0x33, 0x28, 0x47, 0x94, 0xc5, 0xcf, 0xc2, 0xc, 0x69, 0x37, 0x1c, 0xac, 0x50} + +// SetAdminEvent represents "SetAdmin" event emitted by the contract. +type SetAdminEvent struct { + Name string + OldAdmin util.Uint160 + NewAdmin util.Uint160 +} + +// RenewEvent represents "Renew" event emitted by the contract. +type RenewEvent struct { + Name string + OldExpiration *big.Int + NewExpiration *big.Int +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep11.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep11.Actor + + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep11.NonDivisibleReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep11.BaseWriter + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + var nep11ndt = nep11.NewNonDivisible(actor, hash) + return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor, hash}, nep11ndt.BaseWriter, actor, hash} +} + +// Roots invokes `roots` method of contract. +func (c *ContractReader) Roots() (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(c.hash, "roots")) +} + +// RootsExpanded is similar to Roots (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) RootsExpanded(_numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "roots", _numOfIteratorItems)) +} + +// GetPrice invokes `getPrice` method of contract. +func (c *ContractReader) GetPrice(length *big.Int) (*big.Int, error) { + return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice", length)) +} + +// IsAvailable invokes `isAvailable` method of contract. +func (c *ContractReader) IsAvailable(name string) (bool, error) { + return unwrap.Bool(c.invoker.Call(c.hash, "isAvailable", name)) +} + +// GetRecord invokes `getRecord` method of contract. +func (c *ContractReader) GetRecord(name string, typev *big.Int) (string, error) { + return unwrap.UTF8String(c.invoker.Call(c.hash, "getRecord", name, typev)) +} + +// GetAllRecords invokes `getAllRecords` method of contract. +func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name)) +} + +// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name)) +} + +// Resolve invokes `resolve` method of contract. +func (c *ContractReader) Resolve(name string, typev *big.Int) (string, error) { + return unwrap.UTF8String(c.invoker.Call(c.hash, "resolve", name, typev)) +} + +// Update creates a transaction invoking `update` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Update(nef []byte, manifest string) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "update", nef, manifest) +} + +// UpdateTransaction creates a transaction invoking `update` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UpdateTransaction(nef []byte, manifest string) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "update", nef, manifest) +} + +// UpdateUnsigned creates a transaction invoking `update` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) UpdateUnsigned(nef []byte, manifest string) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest) +} + +// AddRoot creates a transaction invoking `addRoot` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) AddRoot(root string) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "addRoot", root) +} + +// AddRootTransaction creates a transaction invoking `addRoot` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) AddRootTransaction(root string) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "addRoot", root) +} + +// AddRootUnsigned creates a transaction invoking `addRoot` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) AddRootUnsigned(root string) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "addRoot", nil, root) +} + +// SetPrice creates a transaction invoking `setPrice` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) SetPrice(priceList []any) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "setPrice", priceList) +} + +// SetPriceTransaction creates a transaction invoking `setPrice` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) SetPriceTransaction(priceList []any) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "setPrice", priceList) +} + +// SetPriceUnsigned creates a transaction invoking `setPrice` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) SetPriceUnsigned(priceList []any) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "setPrice", nil, priceList) +} + +func (c *Contract) scriptForRegister(name string, owner util.Uint160) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner) +} + +// Register creates a transaction invoking `register` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Register(name string, owner util.Uint160) (util.Uint256, uint32, error) { + script, err := c.scriptForRegister(name, owner) + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script) +} + +// RegisterTransaction creates a transaction invoking `register` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) RegisterTransaction(name string, owner util.Uint160) (*transaction.Transaction, error) { + script, err := c.scriptForRegister(name, owner) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) +} + +// RegisterUnsigned creates a transaction invoking `register` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) RegisterUnsigned(name string, owner util.Uint160) (*transaction.Transaction, error) { + script, err := c.scriptForRegister(name, owner) + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil) +} + +// Renew creates a transaction invoking `renew` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Renew(name string) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "renew", name) +} + +// RenewTransaction creates a transaction invoking `renew` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) RenewTransaction(name string) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "renew", name) +} + +// RenewUnsigned creates a transaction invoking `renew` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) RenewUnsigned(name string) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name) +} + +// Renew2 creates a transaction invoking `renew` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Renew2(name string, years *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "renew", name, years) +} + +// Renew2Transaction creates a transaction invoking `renew` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) Renew2Transaction(name string, years *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "renew", name, years) +} + +// Renew2Unsigned creates a transaction invoking `renew` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) Renew2Unsigned(name string, years *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name, years) +} + +// SetAdmin creates a transaction invoking `setAdmin` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) SetAdmin(name string, admin util.Uint160) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "setAdmin", name, admin) +} + +// SetAdminTransaction creates a transaction invoking `setAdmin` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) SetAdminTransaction(name string, admin util.Uint160) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "setAdmin", name, admin) +} + +// SetAdminUnsigned creates a transaction invoking `setAdmin` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) SetAdminUnsigned(name string, admin util.Uint160) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "setAdmin", nil, name, admin) +} + +// SetRecord creates a transaction invoking `setRecord` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) SetRecord(name string, typev *big.Int, data string) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "setRecord", name, typev, data) +} + +// SetRecordTransaction creates a transaction invoking `setRecord` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) SetRecordTransaction(name string, typev *big.Int, data string) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "setRecord", name, typev, data) +} + +// SetRecordUnsigned creates a transaction invoking `setRecord` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) SetRecordUnsigned(name string, typev *big.Int, data string) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "setRecord", nil, name, typev, data) +} + +// DeleteRecord creates a transaction invoking `deleteRecord` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) DeleteRecord(name string, typev *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "deleteRecord", name, typev) +} + +// DeleteRecordTransaction creates a transaction invoking `deleteRecord` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) DeleteRecordTransaction(name string, typev *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "deleteRecord", name, typev) +} + +// DeleteRecordUnsigned creates a transaction invoking `deleteRecord` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) DeleteRecordUnsigned(name string, typev *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "deleteRecord", nil, name, typev) +} + +// SetAdminEventsFromApplicationLog retrieves a set of all emitted events +// with "SetAdmin" name from the provided [result.ApplicationLog]. +func SetAdminEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetAdminEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SetAdminEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SetAdmin" { + continue + } + event := new(SetAdminEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SetAdminEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SetAdminEvent or +// returns an error if it's not possible to do to so. +func (e *SetAdminEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 3 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + e.OldAdmin, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field OldAdmin: %w", err) + } + + index++ + e.NewAdmin, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field NewAdmin: %w", err) + } + + return nil +} + +// RenewEventsFromApplicationLog retrieves a set of all emitted events +// with "Renew" name from the provided [result.ApplicationLog]. +func RenewEventsFromApplicationLog(log *result.ApplicationLog) ([]*RenewEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*RenewEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "Renew" { + continue + } + event := new(RenewEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize RenewEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to RenewEvent or +// returns an error if it's not possible to do to so. +func (e *RenewEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 3 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.Name, err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + e.OldExpiration, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field OldExpiration: %w", err) + } + + index++ + e.NewExpiration, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NewExpiration: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/nameservice/nns.manifest.json b/cli/smartcontract/testdata/nameservice/nns.manifest.json new file mode 100644 index 0000000..555ab89 --- /dev/null +++ b/cli/smartcontract/testdata/nameservice/nns.manifest.json @@ -0,0 +1,441 @@ + { + "abi" : { + "events" : [ + { + "parameters" : [ + { + "name" : "from", + "type" : "Hash160" + }, + { + "name" : "to", + "type" : "Hash160" + }, + { + "name" : "amount", + "type" : "Integer" + }, + { + "type" : "ByteArray", + "name" : "tokenId" + } + ], + "name" : "Transfer" + }, + { + "parameters" : [ + { + "type" : "String", + "name" : "name" + }, + { + "type" : "Hash160", + "name" : "oldAdmin" + }, + { + "type" : "Hash160", + "name" : "newAdmin" + } + ], + "name" : "SetAdmin" + }, + { + "name" : "Renew", + "parameters" : [ + { + "name" : "name", + "type" : "String" + }, + { + "type" : "Integer", + "name" : "oldExpiration" + }, + { + "name" : "newExpiration", + "type" : "Integer" + } + ] + } + ], + "methods" : [ + { + "safe" : true, + "parameters" : [], + "name" : "symbol", + "returntype" : "String", + "offset" : 0 + }, + { + "parameters" : [], + "name" : "decimals", + "returntype" : "Integer", + "safe" : true, + "offset" : 6 + }, + { + "offset" : 8, + "returntype" : "Integer", + "name" : "totalSupply", + "parameters" : [], + "safe" : true + }, + { + "offset" : 53, + "safe" : true, + "parameters" : [ + { + "type" : "ByteArray", + "name" : "tokenId" + } + ], + "name" : "ownerOf", + "returntype" : "Hash160" + }, + { + "safe" : true, + "name" : "properties", + "returntype" : "Map", + "parameters" : [ + { + "type" : "ByteArray", + "name" : "tokenId" + } + ], + "offset" : 184 + }, + { + "safe" : true, + "returntype" : "Integer", + "name" : "balanceOf", + "parameters" : [ + { + "name" : "owner", + "type" : "Hash160" + } + ], + "offset" : 341 + }, + { + "safe" : true, + "returntype" : "InteropInterface", + "name" : "tokens", + "parameters" : [], + "offset" : 453 + }, + { + "safe" : true, + "name" : "tokensOf", + "returntype" : "InteropInterface", + "parameters" : [ + { + "name" : "owner", + "type" : "Hash160" + } + ], + "offset" : 494 + }, + { + "offset" : 600, + "safe" : false, + "parameters" : [ + { + "type" : "Hash160", + "name" : "to" + }, + { + "name" : "tokenId", + "type" : "ByteArray" + }, + { + "name" : "data", + "type" : "Any" + } + ], + "name" : "transfer", + "returntype" : "Boolean" + }, + { + "name" : "update", + "returntype" : "Void", + "parameters" : [ + { + "type" : "ByteArray", + "name" : "nef" + }, + { + "name" : "manifest", + "type" : "String" + } + ], + "safe" : false, + "offset" : 1121 + }, + { + "offset" : 1291, + "returntype" : "Void", + "name" : "addRoot", + "parameters" : [ + { + "name" : "root", + "type" : "String" + } + ], + "safe" : false + }, + { + "offset" : 1725, + "safe" : true, + "parameters" : [], + "returntype" : "InteropInterface", + "name" : "roots" + }, + { + "offset" : 1757, + "parameters" : [ + { + "type" : "Array", + "name" : "priceList" + } + ], + "name" : "setPrice", + "returntype" : "Void", + "safe" : false + }, + { + "offset" : 1952, + "safe" : true, + "parameters" : [ + { + "name" : "length", + "type" : "Integer" + } + ], + "name" : "getPrice", + "returntype" : "Integer" + }, + { + "offset" : 2017, + "safe" : true, + "parameters" : [ + { + "type" : "String", + "name" : "name" + } + ], + "name" : "isAvailable", + "returntype" : "Boolean" + }, + { + "offset" : 2405, + "parameters" : [ + { + "type" : "String", + "name" : "name" + }, + { + "type" : "Hash160", + "name" : "owner" + } + ], + "name" : "register", + "returntype" : "Boolean", + "safe" : false + }, + { + "name" : "renew", + "returntype" : "Integer", + "parameters" : [ + { + "type" : "String", + "name" : "name" + } + ], + "safe" : false, + "offset" : 3113 + }, + { + "offset" : 3123, + "safe" : false, + "parameters" : [ + { + "name" : "name", + "type" : "String" + }, + { + "name" : "years", + "type" : "Integer" + } + ], + "name" : "renew", + "returntype" : "Integer" + }, + { + "offset" : 3697, + "parameters" : [ + { + "type" : "String", + "name" : "name" + }, + { + "name" : "admin", + "type" : "Hash160" + } + ], + "name" : "setAdmin", + "returntype" : "Void", + "safe" : false + }, + { + "safe" : false, + "returntype" : "Void", + "name" : "setRecord", + "parameters" : [ + { + "name" : "name", + "type" : "String" + }, + { + "type" : "Integer", + "name" : "type" + }, + { + "name" : "data", + "type" : "String" + } + ], + "offset" : 3921 + }, + { + "name" : "getRecord", + "returntype" : "String", + "parameters" : [ + { + "name" : "name", + "type" : "String" + }, + { + "type" : "Integer", + "name" : "type" + } + ], + "safe" : true, + "offset" : 5877 + }, + { + "returntype" : "InteropInterface", + "name" : "getAllRecords", + "parameters" : [ + { + "name" : "name", + "type" : "String" + } + ], + "safe" : true, + "offset" : 6201 + }, + { + "safe" : false, + "returntype" : "Void", + "name" : "deleteRecord", + "parameters" : [ + { + "name" : "name", + "type" : "String" + }, + { + "type" : "Integer", + "name" : "type" + } + ], + "offset" : 6281 + }, + { + "offset" : 6565, + "name" : "resolve", + "returntype" : "String", + "parameters" : [ + { + "name" : "name", + "type" : "String" + }, + { + "type" : "Integer", + "name" : "type" + } + ], + "safe" : true + }, + { + "safe" : false, + "parameters" : [ + { + "name" : "data", + "type" : "Any" + }, + { + "name" : "update", + "type" : "Boolean" + } + ], + "name" : "_deploy", + "returntype" : "Void", + "offset" : 7152 + }, + { + "offset" : 7514, + "parameters" : [], + "returntype" : "Void", + "name" : "_initialize", + "safe" : false + } + ] + }, + "supportedstandards" : [ + "NEP-11" + ], + "permissions" : [ + { + "contract" : "0x726cb6e0cd8628a1350a611384688911ab75f51b", + "methods" : [ + "ripemd160" + ] + }, + { + "contract" : "0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0", + "methods" : [ + "atoi", + "deserialize", + "serialize", + "stringSplit" + ] + }, + { + "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", + "methods" : [ + "getCommittee" + ] + }, + { + "contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd", + "methods" : [ + "getContract", + "update" + ] + }, + { + "methods" : [ + "onNEP11Payment" + ], + "contract" : "*" + } + ], + "features" : {}, + "name" : "NameService", + "trusts" : [], + "extra" : { + "Author" : "The Neo Project", + "Description" : "Neo Name Service", + "Email" : "dev@neo.org" + }, + "groups" : [] + } diff --git a/cli/smartcontract/testdata/nex/nex.go b/cli/smartcontract/testdata/nex/nex.go new file mode 100644 index 0000000..078c794 --- /dev/null +++ b/cli/smartcontract/testdata/nex/nex.go @@ -0,0 +1,339 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nextoken contains RPC wrappers for NEX Token contract. +package nextoken + +import ( + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0xa8, 0x1a, 0xa1, 0xf0, 0x4b, 0xf, 0xdc, 0x4a, 0xa2, 0xce, 0xd5, 0xbf, 0xc6, 0x22, 0xcf, 0xe8, 0x9, 0x7f, 0xa6, 0xa2} + +// OnMintEvent represents "OnMint" event emitted by the contract. +type OnMintEvent struct { + From util.Uint160 + To util.Uint160 + Amount *big.Int + SwapId *big.Int +} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep17.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + + nep17.Actor + + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep17.TokenReader + invoker Invoker + hash util.Uint160 +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep17.TokenWriter + actor Actor + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{*nep17.NewReader(invoker, hash), invoker, hash} +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var hash = Hash + var nep17t = nep17.New(actor, hash) + return &Contract{ContractReader{nep17t.TokenReader, actor, hash}, nep17t.TokenWriter, actor, hash} +} + +// Cap invokes `cap` method of contract. +func (c *ContractReader) Cap() (*big.Int, error) { + return unwrap.BigInt(c.invoker.Call(c.hash, "cap")) +} + +// GetMinter invokes `getMinter` method of contract. +func (c *ContractReader) GetMinter() (*keys.PublicKey, error) { + return unwrap.PublicKey(c.invoker.Call(c.hash, "getMinter")) +} + +// GetOwner invokes `getOwner` method of contract. +func (c *ContractReader) GetOwner() (util.Uint160, error) { + return unwrap.Uint160(c.invoker.Call(c.hash, "getOwner")) +} + +// TotalMinted invokes `totalMinted` method of contract. +func (c *ContractReader) TotalMinted() (*big.Int, error) { + return unwrap.BigInt(c.invoker.Call(c.hash, "totalMinted")) +} + +// ChangeMinter creates a transaction invoking `changeMinter` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) ChangeMinter(newMinter *keys.PublicKey) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "changeMinter", newMinter) +} + +// ChangeMinterTransaction creates a transaction invoking `changeMinter` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ChangeMinterTransaction(newMinter *keys.PublicKey) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "changeMinter", newMinter) +} + +// ChangeMinterUnsigned creates a transaction invoking `changeMinter` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) ChangeMinterUnsigned(newMinter *keys.PublicKey) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "changeMinter", nil, newMinter) +} + +// ChangeOwner creates a transaction invoking `changeOwner` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) ChangeOwner(newOwner util.Uint160) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "changeOwner", newOwner) +} + +// ChangeOwnerTransaction creates a transaction invoking `changeOwner` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ChangeOwnerTransaction(newOwner util.Uint160) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "changeOwner", newOwner) +} + +// ChangeOwnerUnsigned creates a transaction invoking `changeOwner` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) ChangeOwnerUnsigned(newOwner util.Uint160) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "changeOwner", nil, newOwner) +} + +// Destroy creates a transaction invoking `destroy` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Destroy() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "destroy") +} + +// DestroyTransaction creates a transaction invoking `destroy` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) DestroyTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "destroy") +} + +// DestroyUnsigned creates a transaction invoking `destroy` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) DestroyUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "destroy", nil) +} + +// MaxSupply creates a transaction invoking `maxSupply` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) MaxSupply() (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "maxSupply") +} + +// MaxSupplyTransaction creates a transaction invoking `maxSupply` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MaxSupplyTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "maxSupply") +} + +// MaxSupplyUnsigned creates a transaction invoking `maxSupply` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) MaxSupplyUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "maxSupply", nil) +} + +// Mint creates a transaction invoking `mint` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Mint(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data any) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "mint", from, to, amount, swapId, signature, data) +} + +// MintTransaction creates a transaction invoking `mint` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MintTransaction(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data any) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "mint", from, to, amount, swapId, signature, data) +} + +// MintUnsigned creates a transaction invoking `mint` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) MintUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data any) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "mint", nil, from, to, amount, swapId, signature, data) +} + +// Update creates a transaction invoking `update` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Update(nef []byte, manifest []byte) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "update", nef, manifest) +} + +// UpdateTransaction creates a transaction invoking `update` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UpdateTransaction(nef []byte, manifest []byte) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "update", nef, manifest) +} + +// UpdateUnsigned creates a transaction invoking `update` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) UpdateUnsigned(nef []byte, manifest []byte) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest) +} + +// UpdateCap creates a transaction invoking `updateCap` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) UpdateCap(newCap *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "updateCap", newCap) +} + +// UpdateCapTransaction creates a transaction invoking `updateCap` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UpdateCapTransaction(newCap *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "updateCap", newCap) +} + +// UpdateCapUnsigned creates a transaction invoking `updateCap` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) UpdateCapUnsigned(newCap *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "updateCap", nil, newCap) +} + +// OnMintEventsFromApplicationLog retrieves a set of all emitted events +// with "OnMint" name from the provided [result.ApplicationLog]. +func OnMintEventsFromApplicationLog(log *result.ApplicationLog) ([]*OnMintEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*OnMintEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "OnMint" { + continue + } + event := new(OnMintEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize OnMintEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to OnMintEvent or +// returns an error if it's not possible to do to so. +func (e *OnMintEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 4 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.From, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field From: %w", err) + } + + index++ + e.To, err = func(item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field To: %w", err) + } + + index++ + e.Amount, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Amount: %w", err) + } + + index++ + e.SwapId, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SwapId: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/nex/nex.manifest.json b/cli/smartcontract/testdata/nex/nex.manifest.json new file mode 100644 index 0000000..83201b3 --- /dev/null +++ b/cli/smartcontract/testdata/nex/nex.manifest.json @@ -0,0 +1,275 @@ + { + "name" : "NEX Token", + "abi" : { + "events" : [ + { + "parameters" : [ + { + "type" : "Hash160", + "name" : "from" + }, + { + "name" : "to", + "type" : "Hash160" + }, + { + "name" : "amount", + "type" : "Integer" + } + ], + "name" : "Transfer" + }, + { + "name" : "OnMint", + "parameters" : [ + { + "name" : "from", + "type" : "Hash160" + }, + { + "type" : "Hash160", + "name" : "to" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "name" : "swapId", + "type" : "Integer" + } + ] + } + ], + "methods" : [ + { + "parameters" : [], + "offset" : 0, + "name" : "_initialize", + "safe" : false, + "returntype" : "Void" + }, + { + "parameters" : [ + { + "type" : "Any", + "name" : "data" + }, + { + "name" : "isUpdate", + "type" : "Boolean" + } + ], + "offset" : 3, + "name" : "_deploy", + "safe" : false, + "returntype" : "Void" + }, + { + "parameters" : [ + { + "type" : "Hash160", + "name" : "holder" + } + ], + "offset" : 484, + "returntype" : "Integer", + "safe" : true, + "name" : "balanceOf" + }, + { + "safe" : true, + "returntype" : "Integer", + "name" : "cap", + "offset" : 1881, + "parameters" : [] + }, + { + "name" : "changeMinter", + "safe" : false, + "returntype" : "Void", + "parameters" : [ + { + "name" : "newMinter", + "type" : "PublicKey" + } + ], + "offset" : 1678 + }, + { + "parameters" : [ + { + "type" : "Hash160", + "name" : "newOwner" + } + ], + "offset" : 1659, + "name" : "changeOwner", + "safe" : false, + "returntype" : "Void" + }, + { + "offset" : 466, + "parameters" : [], + "safe" : true, + "name" : "decimals", + "returntype" : "Integer" + }, + { + "returntype" : "Void", + "safe" : false, + "name" : "destroy", + "parameters" : [], + "offset" : 1194 + }, + { + "safe" : true, + "returntype" : "PublicKey", + "name" : "getMinter", + "offset" : 1671, + "parameters" : [] + }, + { + "parameters" : [], + "offset" : 1652, + "name" : "getOwner", + "safe" : true, + "returntype" : "Hash160" + }, + { + "name" : "maxSupply", + "safe" : false, + "returntype" : "Integer", + "parameters" : [], + "offset" : 468 + }, + { + "offset" : 1222, + "parameters" : [ + { + "name" : "from", + "type" : "Hash160" + }, + { + "name" : "to", + "type" : "Hash160" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "name" : "swapId", + "type" : "Integer" + }, + { + "name" : "signature", + "type" : "Signature" + }, + { + "name" : "data", + "type" : "Any" + } + ], + "safe" : false, + "name" : "mint", + "returntype" : "Void" + }, + { + "safe" : true, + "name" : "symbol", + "returntype" : "String", + "parameters" : [], + "offset" : 460 + }, + { + "offset" : 1854, + "parameters" : [], + "name" : "totalMinted", + "safe" : true, + "returntype" : "Integer" + }, + { + "offset" : 478, + "parameters" : [], + "name" : "totalSupply", + "safe" : true, + "returntype" : "Integer" + }, + { + "offset" : 543, + "parameters" : [ + { + "name" : "from", + "type" : "Hash160" + }, + { + "name" : "to", + "type" : "Hash160" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "name" : "data", + "type" : "Any" + } + ], + "name" : "transfer", + "safe" : false, + "returntype" : "Boolean" + }, + { + "offset" : 1205, + "parameters" : [ + { + "type" : "ByteArray", + "name" : "nef" + }, + { + "name" : "manifest", + "type" : "ByteArray" + } + ], + "safe" : false, + "returntype" : "Void", + "name" : "update" + }, + { + "offset" : 1717, + "parameters" : [ + { + "type" : "Integer", + "name" : "newCap" + } + ], + "returntype" : "Void", + "safe" : false, + "name" : "updateCap" + } + ] + }, + "supportedstandards" : [ + "NEP-17" + ], + "extra" : null, + "trusts" : [], + "features" : {}, + "groups" : [], + "permissions" : [ + { + "methods" : [ + "onNEP17Payment" + ], + "contract" : "*" + }, + { + "methods" : [ + "update", + "destroy" + ], + "contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd" + } + ] + } diff --git a/cli/smartcontract/testdata/nonepiter/iter.go b/cli/smartcontract/testdata/nonepiter/iter.go new file mode 100644 index 0000000..bf8e7c2 --- /dev/null +++ b/cli/smartcontract/testdata/nonepiter/iter.go @@ -0,0 +1,63 @@ +// Code generated by neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. + +// Package nonnepxxcontractwithiterators contains RPC wrappers for Non-NEPXX contract with iterators contract. +package nonnepxxcontractwithiterators + +import ( + "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) + CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error) + TerminateSession(sessionID uuid.UUID) error + TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker + hash util.Uint160 +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + var hash = Hash + return &ContractReader{invoker, hash} +} + +// Tokens invokes `tokens` method of contract. +func (c *ContractReader) Tokens() (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(c.hash, "tokens")) +} + +// TokensExpanded is similar to Tokens (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) TokensExpanded(_numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "tokens", _numOfIteratorItems)) +} + +// GetAllRecords invokes `getAllRecords` method of contract. +func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name)) +} + +// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name)) +} diff --git a/cli/smartcontract/testdata/nonepiter/iter.manifest.json b/cli/smartcontract/testdata/nonepiter/iter.manifest.json new file mode 100644 index 0000000..9f33e27 --- /dev/null +++ b/cli/smartcontract/testdata/nonepiter/iter.manifest.json @@ -0,0 +1,33 @@ +{ + "groups" : [], + "abi" : { + "events" : [], + "methods" : [ + { + "parameters" : [], + "safe" : true, + "name" : "tokens", + "offset" : 0, + "returntype" : "InteropInterface" + }, + { + "offset" : 1, + "returntype" : "InteropInterface", + "safe" : true, + "parameters" : [ + { + "type" : "String", + "name" : "name" + } + ], + "name" : "getAllRecords" + } + ] + }, + "supportedstandards" : [], + "trusts" : [], + "extra" : {}, + "permissions" : [], + "name" : "Non-NEPXX contract with iterators", + "features" : {} +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid1/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid1/invalid.go new file mode 100644 index 0000000..821a7d8 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid1/invalid.go @@ -0,0 +1,7 @@ +package invalid1 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +func Main() { + runtime.Notify("Non declared event") +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid1/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid1/invalid.yml new file mode 100644 index 0000000..eda9482 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid1/invalid.yml @@ -0,0 +1 @@ +name: Test undeclared event \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/invalid2/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid2/invalid.go new file mode 100644 index 0000000..6aa771c --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid2/invalid.go @@ -0,0 +1,7 @@ +package invalid2 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +func Main() { + runtime.Notify("SomeEvent", "p1", "p2") +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid2/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid2/invalid.yml new file mode 100644 index 0000000..1393380 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid2/invalid.yml @@ -0,0 +1,6 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: String \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/invalid3/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid3/invalid.go new file mode 100644 index 0000000..7f9298f --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid3/invalid.go @@ -0,0 +1,7 @@ +package invalid3 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +func Main() { + runtime.Notify("SomeEvent", "p1", 5) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid3/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid3/invalid.yml new file mode 100644 index 0000000..a217dee --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid3/invalid.yml @@ -0,0 +1,8 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: String + - name: p2 + type: String \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/invalid4/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid4/invalid.go new file mode 100644 index 0000000..70f887b --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid4/invalid.go @@ -0,0 +1,17 @@ +package invalid4 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type SomeStruct1 struct { + Field1 int +} + +type SomeStruct2 struct { + Field2 string +} + +func Main() { + // Inconsistent event params usages (different named types throughout the usages). + runtime.Notify("SomeEvent", SomeStruct1{Field1: 123}) + runtime.Notify("SomeEvent", SomeStruct2{Field2: "str"}) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid4/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid4/invalid.yml new file mode 100644 index 0000000..6c0c347 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid4/invalid.yml @@ -0,0 +1,6 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: Array \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.go new file mode 100644 index 0000000..3d31565 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.go @@ -0,0 +1,12 @@ +package invalid5 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type NamedStruct struct { + SomeInt int +} + +func Main() NamedStruct { + runtime.Notify("SomeEvent", []interface{}{123}) + return NamedStruct{SomeInt: 123} +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml new file mode 100644 index 0000000..ccd05f4 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml @@ -0,0 +1,16 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: Array + extendedtype: + base: Array + name: invalid5.NamedStruct +namedtypes: + invalid5.NamedStruct: + base: Array + name: invalid5.NamedStruct + fields: + - field: SomeInt + base: Integer diff --git a/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go new file mode 100644 index 0000000..2b210e7 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go @@ -0,0 +1,14 @@ +package invalid6 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type SomeStruct struct { + Field int + // RPC binding generator will convert this field into exported, which matches + // exactly the existing Field. + field int +} + +func Main() { + runtime.Notify("SomeEvent", SomeStruct{Field: 123, field: 123}) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml new file mode 100644 index 0000000..c9fe64b --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml @@ -0,0 +1,18 @@ +name: Test duplicating event fields +events: + - name: SomeEvent + parameters: + - name: p1 + type: Struct + extendedtype: + base: Struct + name: SomeStruct +namedtypes: + SomeStruct: + base: Struct + name: SomeStruct + fields: + - field: Field + base: Integer + - field: field + base: Integer diff --git a/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go new file mode 100644 index 0000000..6cb9e6a --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go @@ -0,0 +1,14 @@ +package invalid7 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type SomeStruct struct { + Field int + // RPC binding generator will convert this field into exported, which matches + // exactly the existing Field. + field int +} + +func Main() { + runtime.Notify("SomeEvent", SomeStruct{Field: 123, field: 123}) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml new file mode 100644 index 0000000..cbc8c56 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml @@ -0,0 +1,6 @@ +name: Test duplicating autogenerated event fields +events: + - name: SomeEvent + parameters: + - name: p1 + type: Struct diff --git a/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go new file mode 100644 index 0000000..db96523 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go @@ -0,0 +1,16 @@ +package invalid8 + +type SomeStruct struct { + Field int + // RPC binding generator will convert this field into exported, which matches + // exactly the existing Field. + field int +} + +func Main() SomeStruct { + s := SomeStruct{ + Field: 1, + field: 2, + } + return s +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml new file mode 100644 index 0000000..2b308a5 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml @@ -0,0 +1 @@ +name: Test duplicating struct fields diff --git a/cli/smartcontract/testdata/rpcbindings/notifications/config.yml b/cli/smartcontract/testdata/rpcbindings/notifications/config.yml new file mode 100644 index 0000000..40b13bf --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/notifications/config.yml @@ -0,0 +1,23 @@ +name: "Notifications" +sourceurl: https://github.com/nspcc-dev/neo-go/ +events: + - name: "! complicated name %$#" + parameters: + - name: ! complicated param @#$% + type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + - name: "SomeStruct" + parameters: + - name: s + type: Struct + - name: "SomeArray" + parameters: + - name: a + type: Array + - name: "SomeUnexportedField" + parameters: + - name: s + type: Struct \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml b/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml new file mode 100644 index 0000000..6c6e418 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml @@ -0,0 +1,60 @@ +name: "Notifications" +sourceurl: https://github.com/nspcc-dev/neo-go/ +events: + - name: "! complicated name %$#" + parameters: + - name: ! complicated param @#$% + type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + extendedtype: + base: Map + key: Integer + value: + base: Map + key: String + value: + base: Array + value: + base: Hash160 + - name: "SomeStruct" + parameters: + - name: s + type: Struct + extendedtype: + base: Struct + name: crazyStruct + - name: "SomeArray" + parameters: + - name: a + type: Array + extendedtype: + base: Array + value: + base: Array + value: + base: Integer + - name: "SomeUnexportedField" + parameters: + - name: s + type: Struct + extendedtype: + base: Struct + name: simpleStruct +namedtypes: + crazyStruct: + base: Struct + name: crazyStruct + fields: + - field: I + base: Integer + - field: B + base: Boolean + simpleStruct: + base: Struct + name: simpleStruct + fields: + - field: i + base: Integer \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/notifications/config_guessed.yml b/cli/smartcontract/testdata/rpcbindings/notifications/config_guessed.yml new file mode 100644 index 0000000..40b13bf --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/notifications/config_guessed.yml @@ -0,0 +1,23 @@ +name: "Notifications" +sourceurl: https://github.com/nspcc-dev/neo-go/ +events: + - name: "! complicated name %$#" + parameters: + - name: ! complicated param @#$% + type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + - name: "SomeStruct" + parameters: + - name: s + type: Struct + - name: "SomeArray" + parameters: + - name: a + type: Array + - name: "SomeUnexportedField" + parameters: + - name: s + type: Struct \ No newline at end of file diff --git a/cli/smartcontract/testdata/rpcbindings/notifications/notifications.go b/cli/smartcontract/testdata/rpcbindings/notifications/notifications.go new file mode 100644 index 0000000..e88a413 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/notifications/notifications.go @@ -0,0 +1,33 @@ +package structs + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +func Main() { + runtime.Notify("! complicated name %$#", "str1") +} + +func CrazyMap() { + runtime.Notify("SomeMap", map[int][]map[string][]interop.Hash160{}) +} + +func Struct() { + runtime.Notify("SomeStruct", struct { + I int + B bool + }{I: 123, B: true}) +} + +func Array() { + runtime.Notify("SomeArray", [][]int{}) +} + +// UnexportedField emits notification with unexported field that must be converted +// to exported in the resulting RPC binding. +func UnexportedField() { + runtime.Notify("SomeUnexportedField", struct { + i int + }{i: 123}) +} diff --git a/cli/smartcontract/testdata/rpcbindings/royalty/config.yml b/cli/smartcontract/testdata/rpcbindings/royalty/config.yml new file mode 100644 index 0000000..a121845 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/royalty/config.yml @@ -0,0 +1,16 @@ +name: Test royalty +sourceurl: https://github.com/nspcc-dev/neo-go/ +supportedstandards: ["NEP-24-Payable"] +events: + - name: RoyaltiesTransferred + parameters: + - name: royaltyToken + type: Hash160 + - name: royaltyRecipient + type: Hash160 + - name: buyer + type: Hash160 + - name: tokenId + type: ByteArray + - name: amount + type: Integer diff --git a/cli/smartcontract/testdata/rpcbindings/royalty/royalty.go b/cli/smartcontract/testdata/rpcbindings/royalty/royalty.go new file mode 100644 index 0000000..835a527 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/royalty/royalty.go @@ -0,0 +1,13 @@ +package royalty + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/native/std" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +// RoyaltiesTransferred notifies about royalty payment. This method is called by marketplace +// contract when royalties are transferred. +func RoyaltiesTransferred(royaltyToken, royaltyRecipient, buyer interop.Hash160, tokenId []byte, amount int) { + runtime.Notify("RoyaltiesTransferred", royaltyToken, royaltyRecipient, buyer, std.Deserialize(tokenId), amount) +} diff --git a/cli/smartcontract/testdata/rpcbindings/structs/config.yml b/cli/smartcontract/testdata/rpcbindings/structs/config.yml new file mode 100644 index 0000000..04192ba --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/structs/config.yml @@ -0,0 +1,3 @@ +name: "Types" +sourceurl: https://github.com/nspcc-dev/neo-go/ +safemethods: ["contract", "block", "transaction", "struct"] diff --git a/cli/smartcontract/testdata/rpcbindings/structs/structs.go b/cli/smartcontract/testdata/rpcbindings/structs/structs.go new file mode 100644 index 0000000..cf3fa28 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/structs/structs.go @@ -0,0 +1,40 @@ +package structs + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" +) + +type Internal struct { + Bool bool + Int int + Bytes []byte + String string + H160 interop.Hash160 + H256 interop.Hash256 + PK interop.PublicKey + PubKey interop.PublicKey + Sign interop.Signature + ArrOfBytes [][]byte + ArrOfH160 []interop.Hash160 + Map map[int][]interop.PublicKey + Struct *Internal + unexportedField int // this one should be exported in the resulting RPC binding. +} + +func Contract(mc management.Contract) management.Contract { + return mc +} + +func Block(b *ledger.Block) *ledger.Block { + return b +} + +func Transaction(t *ledger.Transaction) *ledger.Transaction { + return t +} + +func Struct(s *Internal) *Internal { + return s +} diff --git a/cli/smartcontract/testdata/rpcbindings/types/config.yml b/cli/smartcontract/testdata/rpcbindings/types/config.yml new file mode 100644 index 0000000..863043b --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/types/config.yml @@ -0,0 +1,3 @@ +name: "Types" +sourceurl: https://github.com/nspcc-dev/neo-go/ +safemethods: ["bool", "int", "bytes", "string", "any", "hash160", "hash256", "publicKey", "signature", "bools", "ints", "bytess", "strings", "hash160s", "hash256s", "publicKeys", "signatures", "aAAStrings", "maps", "crazyMaps", "anyMaps", "unnamedStructs", "unnamedStructsX"] diff --git a/cli/smartcontract/testdata/rpcbindings/types/types.go b/cli/smartcontract/testdata/rpcbindings/types/types.go new file mode 100644 index 0000000..fdba199 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/types/types.go @@ -0,0 +1,103 @@ +package types + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" +) + +func Bool(b bool) bool { + return false +} + +func Int(i int) int { + return 0 +} + +func Bytes(b []byte) []byte { + return nil +} + +func String(s string) string { + return "" +} + +func Any(a any) any { + return nil +} + +func Hash160(h interop.Hash160) interop.Hash160 { + return nil +} + +func Hash256(h interop.Hash256) interop.Hash256 { + return nil +} + +func PublicKey(k interop.PublicKey) interop.PublicKey { + return nil +} + +func Signature(s interop.Signature) interop.Signature { + return nil +} + +func Bools(b []bool) []bool { + return nil +} + +func Ints(i []int) []int { + return nil +} + +func Bytess(b [][]byte) [][]byte { + return nil +} + +func Strings(s []string) []string { + return nil +} + +func Hash160s(h []interop.Hash160) []interop.Hash160 { + return nil +} + +func Hash256s(h []interop.Hash256) []interop.Hash256 { + return nil +} + +func PublicKeys(k []interop.PublicKey) []interop.PublicKey { + return nil +} + +func Signatures(s []interop.Signature) []interop.Signature { + return nil +} + +func AAAStrings(s [][][]string) [][][]string { + return s +} + +func Maps(m map[string]string) map[string]string { + return m +} + +func CrazyMaps(m map[int][]map[string][]interop.Hash160) map[int][]map[string][]interop.Hash160 { + return m +} + +func AnyMaps(m map[int]any) map[int]any { + return m +} + +func UnnamedStructs() struct{ I int } { + return struct{ I int }{I: 123} +} + +func UnnamedStructsX() struct { + I int + B bool +} { + return struct { + I int + B bool + }{I: 123, B: true} +} diff --git a/cli/smartcontract/testdata/verify.bindings.yml b/cli/smartcontract/testdata/verify.bindings.yml new file mode 100755 index 0000000..927064b --- /dev/null +++ b/cli/smartcontract/testdata/verify.bindings.yml @@ -0,0 +1,297 @@ +package: testdata +hash: "0x0000000000000000000000000000000000000000" +overrides: + burnGas.gas: int + call: any + call.args: '[]any' + call.f: any + call.method: string + call.scriptHash: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + callWithToken: any + callWithToken.args: '[]any' + callWithToken.flags: int + callWithToken.method: string + callWithToken.scriptHash: string + callWithTokenNoRet.args: '[]any' + callWithTokenNoRet.flags: int + callWithTokenNoRet.method: string + callWithTokenNoRet.scriptHash: string + checkWitness: bool + checkWitness.hashOrKey: '[]byte' + createMultisigAccount: '[]byte' + createMultisigAccount.m: int + createMultisigAccount.pubs: '[]github.com/nspcc-dev/neo-go/pkg/interop.PublicKey' + createStandardAccount: '[]byte' + createStandardAccount.pub: github.com/nspcc-dev/neo-go/pkg/interop.PublicKey + currentHash: github.com/nspcc-dev/neo-go/pkg/interop.Hash256 + currentIndex: int + currentSigners: '[]github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.TransactionSigner' + equals: bool + equals.b: any + gasLeft: int + getAddressVersion: int + getBlock: '*github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.Block' + getBlock.indexOrHash: any + getCallFlags: any + getCallingScriptHash: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + getEntryScriptHash: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + getExecutingScriptHash: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + getInvocationCounter: int + getNetwork: int + getNotifications: '[][]any' + getNotifications.h: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + getRandom: int + getScriptContainer: '*github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.Transaction' + getTime: int + getTransaction: '*github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.Transaction' + getTransaction.hash: github.com/nspcc-dev/neo-go/pkg/interop.Hash256 + getTransactionFromBlock: '*github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.Transaction' + getTransactionFromBlock.indexOrHash: any + getTransactionFromBlock.txIndex: int + getTransactionHeight: int + getTransactionHeight.hash: github.com/nspcc-dev/neo-go/pkg/interop.Hash256 + getTransactionSigners: '[]github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.TransactionSigner' + getTransactionSigners.hash: github.com/nspcc-dev/neo-go/pkg/interop.Hash256 + getTransactionVMState: int + getTransactionVMState.hash: github.com/nspcc-dev/neo-go/pkg/interop.Hash256 + getTrigger: int + loadScript: any + loadScript.args: '[]any' + loadScript.f: any + loadScript.script: '[]byte' + log.message: string + notify.args: '[]any' + notify.name: string + onNEP11Payment.amount: int + onNEP11Payment.data: any + onNEP11Payment.from: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + onNEP11Payment.token: '[]byte' + onNEP17Payment.amount: int + onNEP17Payment.data: any + onNEP17Payment.from: github.com/nspcc-dev/neo-go/pkg/interop.Hash160 + opcode0NoReturn.op: string + opcode1: any + opcode1NoReturn.arg: any + opcode1NoReturn.op: string + opcode1.arg: any + opcode1.op: string + opcode2: any + opcode2NoReturn.arg1: any + opcode2NoReturn.arg2: any + opcode2NoReturn.op: string + opcode2.arg1: any + opcode2.arg2: any + opcode2.op: string + opcode3: any + opcode3.arg1: any + opcode3.arg2: any + opcode3.arg3: any + opcode3.op: string + platform: '[]byte' + syscall0: any + syscall0NoReturn.name: string + syscall0.name: string + syscall1: any + syscall1NoReturn.arg: any + syscall1NoReturn.name: string + syscall1.arg: any + syscall1.name: string + syscall2: any + syscall2NoReturn.arg1: any + syscall2NoReturn.arg2: any + syscall2NoReturn.name: string + syscall2.arg1: any + syscall2.arg2: any + syscall2.name: string + syscall3: any + syscall3NoReturn.arg1: any + syscall3NoReturn.arg2: any + syscall3NoReturn.arg3: any + syscall3NoReturn.name: string + syscall3.arg1: any + syscall3.arg2: any + syscall3.arg3: any + syscall3.name: string + syscall4: any + syscall4NoReturn.arg1: any + syscall4NoReturn.arg2: any + syscall4NoReturn.arg3: any + syscall4NoReturn.arg4: any + syscall4NoReturn.name: string + syscall4.arg1: any + syscall4.arg2: any + syscall4.arg3: any + syscall4.arg4: any + syscall4.name: string + toBlockSR: '*github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.BlockSR' + verify: bool +namedtypes: + ledger.Block: + base: Array + name: ledger.Block + fields: + - field: Hash + base: Hash256 + - field: Version + base: Integer + - field: PrevHash + base: Hash256 + - field: MerkleRoot + base: Hash256 + - field: Timestamp + base: Integer + - field: Nonce + base: Integer + - field: Index + base: Integer + - field: NextConsensus + base: Hash160 + - field: TransactionsLength + base: Integer + ledger.BlockSR: + base: Array + name: ledger.BlockSR + fields: + - field: Hash + base: Hash256 + - field: Version + base: Integer + - field: PrevHash + base: Hash256 + - field: MerkleRoot + base: Hash256 + - field: Timestamp + base: Integer + - field: Nonce + base: Integer + - field: Index + base: Integer + - field: NextConsensus + base: Hash160 + - field: TransactionsLength + base: Integer + - field: PrevStateRoot + base: Hash256 + ledger.Transaction: + base: Array + name: ledger.Transaction + fields: + - field: Hash + base: Hash256 + - field: Version + base: Integer + - field: Nonce + base: Integer + - field: Sender + base: Hash160 + - field: SysFee + base: Integer + - field: NetFee + base: Integer + - field: ValidUntilBlock + base: Integer + - field: Script + base: ByteArray + ledger.TransactionSigner: + base: Array + name: ledger.TransactionSigner + fields: + - field: Account + base: Hash160 + - field: Scopes + base: Integer + - field: AllowedContracts + base: Array + value: + base: Hash160 + - field: AllowedGroups + base: Array + value: + base: PublicKey + - field: Rules + base: Array + value: + base: Array + name: ledger.WitnessRule + ledger.WitnessCondition: + base: Array + name: ledger.WitnessCondition + fields: + - field: Type + base: Integer + - field: Value + base: Any + ledger.WitnessRule: + base: Array + name: ledger.WitnessRule + fields: + - field: Action + base: Integer + - field: Condition + base: Array + name: ledger.WitnessCondition +types: + call.args: + base: Array + value: + base: Any + call.f: + base: InteropInterface + interface: iterator + callWithToken.args: + base: Array + value: + base: Any + callWithTokenNoRet.args: + base: Array + value: + base: Any + createMultisigAccount.pubs: + base: Array + value: + base: PublicKey + currentSigners: + base: Array + value: + base: Array + name: ledger.TransactionSigner + getBlock: + base: Array + name: ledger.Block + getCallFlags: + base: InteropInterface + interface: iterator + getNotifications: + base: Array + value: + base: Array + value: + base: Any + getScriptContainer: + base: Array + name: ledger.Transaction + getTransaction: + base: Array + name: ledger.Transaction + getTransactionFromBlock: + base: Array + name: ledger.Transaction + getTransactionSigners: + base: Array + value: + base: Array + name: ledger.TransactionSigner + loadScript.args: + base: Array + value: + base: Any + loadScript.f: + base: InteropInterface + interface: iterator + notify.args: + base: Array + value: + base: Any + toBlockSR: + base: Array + name: ledger.BlockSR diff --git a/cli/smartcontract/testdata/verify.go b/cli/smartcontract/testdata/verify.go new file mode 100644 index 0000000..65cd0d5 --- /dev/null +++ b/cli/smartcontract/testdata/verify.go @@ -0,0 +1,20 @@ +package testdata + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +func Verify() bool { + return true +} + +func OnNEP17Payment(from interop.Hash160, amount int, data any) { +} + +// OnNEP11Payment notifies about NEP-11 payment. You don't call this method directly, +// instead it's called by NEP-11 contract when you transfer funds from your address +// to the address of this NFT contract. +func OnNEP11Payment(from interop.Hash160, amount int, tokenId []byte, data any) { + runtime.Notify("OnNEP11Payment", from, amount, tokenId, data) +} diff --git a/cli/smartcontract/testdata/verify.manifest.json b/cli/smartcontract/testdata/verify.manifest.json new file mode 100755 index 0000000..d6363f7 --- /dev/null +++ b/cli/smartcontract/testdata/verify.manifest.json @@ -0,0 +1 @@ +{"name":"verify","abi":{"methods":[{"name":"verify","offset":0,"parameters":[],"returntype":"Boolean","safe":false},{"name":"onNEP17Payment","offset":5,"parameters":[{"name":"from","type":"ByteArray"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false}],"events":[{"name":"Hello world!","parameters":[{"name":"args","type":"Array"}]}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null,"features":{}} diff --git a/cli/smartcontract/testdata/verify.nef b/cli/smartcontract/testdata/verify.nef new file mode 100755 index 0000000..5023c45 Binary files /dev/null and b/cli/smartcontract/testdata/verify.nef differ diff --git a/cli/smartcontract/testdata/verify.yml b/cli/smartcontract/testdata/verify.yml new file mode 100644 index 0000000..421e5d2 --- /dev/null +++ b/cli/smartcontract/testdata/verify.yml @@ -0,0 +1,12 @@ +name: Test verify +events: + - name: OnNEP11Payment + parameters: + - name: from + type: Hash160 + - name: amount + type: Integer + - name: tokenId + type: ByteArray + - name: data + type: Any diff --git a/cli/smartcontract/testdata/verifyrpc/verify.manifest.json b/cli/smartcontract/testdata/verifyrpc/verify.manifest.json new file mode 100755 index 0000000..221dce8 --- /dev/null +++ b/cli/smartcontract/testdata/verifyrpc/verify.manifest.json @@ -0,0 +1,80 @@ +{ + "groups" : [], + "extra" : null, + "supportedstandards" : [], + "name" : "verify", + "trusts" : [], + "features": {}, + "permissions" : [ + { + "methods" : "*", + "contract" : "*" + } + ], + "abi" : { + "methods" : [ + { + "safe" : false, + "offset" : 0, + "parameters" : [], + "name" : "verify", + "returntype" : "Boolean" + }, + { + "returntype" : "Void", + "safe" : false, + "offset" : 5, + "parameters" : [ + { + "type" : "Hash160", + "name" : "from" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "type" : "Any", + "name" : "data" + } + ], + "name" : "onNEP17Payment" + }, + { + "returntype" : "Void", + "safe" : false, + "offset" : 5, + "parameters" : [ + { + "type" : "Hash160", + "name" : "from" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "type" : "ByteArray", + "name" : "tokenId" + }, + { + "type" : "Any", + "name" : "data" + } + ], + "name" : "onNEP11Payment" + } + ], + "events" : [ + { + "parameters" : [ + { + "type" : "Array", + "name" : "args" + } + ], + "name" : "Hello world!" + } + ] + } +} diff --git a/cli/testdata/testwallet.json b/cli/testdata/testwallet.json new file mode 100644 index 0000000..8e089ef --- /dev/null +++ b/cli/testdata/testwallet.json @@ -0,0 +1,30 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "Nfyz4KcsgYepRJw1W5C2uKCi6QWKf7v6gG", + "key": "6PYVjvHyAFQPXCU3PNsiRbF1RxEyyrT4PqLbSEEZcyYDFAMpSxPZnuRznY", + "label": "kek", + "contract": { + "script": "DCECl3UyEIq6T5RRIXS6z4tNdZPTzQ7NvXyx7FwK05d9UyZBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 2, + "r": 1, + "p": 1 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/cli/testdata/testwallet_multi.json b/cli/testdata/testwallet_multi.json new file mode 100644 index 0000000..8d1400b --- /dev/null +++ b/cli/testdata/testwallet_multi.json @@ -0,0 +1,64 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "NgHcPxgEKZQV4QBedzyASJrgiANhJqBVLw", + "key": "6PYTbVq2P3AJQwWU5SFMKLjHYco7QABtNRo4ZvLvXhyaYjwMcuZm6xKokT", + "label": "one", + "contract": { + "script": "DCECnmSGVirDOqMr57EHaYz0YMTjaHQtO9FQYu8DMTCDw6VBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NLvHRfKAifjio2z9HiwLo9ZnpRPHUbAHgH", + "key": "6PYUjQ8TgR3cduEpG5niUNuPEWi3tYiQsnC4Jha9nGAJ6tAQGUmcrZXsLF", + "label": "two", + "contract": { + "script": "DCECgk91c1ABAX3A1uJNnxhGlp7NwUJScwJzJhrsYrXIbgNBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NcDfG8foJx79XSihcDDrx1df7cHAoJBfXj", + "key": "6PYRkUQKWFrTovHyeQZ7X4nWoDXKohtFRKW51LiCz317pwCjmB1cVwpcxz", + "label": "three", + "contract": { + "script": "DCEC9v0ZqBg8f4jJX9WR891M0bazf0FYTNu7MEgpSHrb9CVBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 2, + "r": 1, + "p": 1 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/cli/testdata/wallet1_solo.json b/cli/testdata/wallet1_solo.json new file mode 100644 index 0000000..9b6d6e0 --- /dev/null +++ b/cli/testdata/wallet1_solo.json @@ -0,0 +1,83 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", + "key": "6PYM8VdX3hY4B51UJxmm8D41RQMbpJT8aYHibyQ67gjkUPmvQgu51Y5UQR", + "label": "", + "contract": { + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": true + }, + { + "address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", + "key": "6PYM8VdX3hY4B51UJxmm8D41RQMbpJT8aYHibyQ67gjkUPmvQgu51Y5UQR", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP", + "key": "6PYM8VdX3hY4B51UJxmm8D41RQMbpJT8aYHibyQ67gjkUPmvQgu51Y5UQR", + "label": "", + "contract": { + "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUGe0Nw6", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + }, + { + "address": "NiFxRcC5Anz9pmqQyMHh5vamBUZDbRRRzA", + "key": "6PYSATFztBa3CHjSR6sLAKungUEAbQUCVE16KzmaQQ38gLeYGZ9Knd5mGv", + "label": "verify", + "contract": { + "parameters": [], + "deployed": true + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 2, + "r": 1, + "p": 1 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/cli/txctx/tx.go b/cli/txctx/tx.go new file mode 100644 index 0000000..dcac2f6 --- /dev/null +++ b/cli/txctx/tx.go @@ -0,0 +1,115 @@ +/* +Package txctx contains helper functions that deal with transactions in CLI context. +*/ +package txctx + +import ( + "fmt" + "io" + "time" + + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" + "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/urfave/cli/v2" +) + +var ( + // GasFlag is a flag used for the additional network fee. + GasFlag = &flags.Fixed8Flag{ + Name: "gas", + Aliases: []string{"g"}, + Usage: "Network fee to add to the transaction (prioritizing it)", + } + // SysGasFlag is a flag used for the additional system fee. + SysGasFlag = &flags.Fixed8Flag{ + Name: "sysgas", + Aliases: []string{"e"}, + Usage: "System fee to add to the transaction (compensating for execution)", + } + // OutFlag is a flag used for file output. + OutFlag = &cli.StringFlag{ + Name: "out", + Usage: "File (JSON) to put signature context with a transaction to", + } + // ForceFlag is a flag used to force transaction send. + ForceFlag = &cli.BoolFlag{ + Name: "force", + Usage: "Do not ask for a confirmation (and ignore errors)", + } + // AwaitFlag is a flag used to wait for the transaction to be included in a block. + AwaitFlag = &cli.BoolFlag{ + Name: "await", + Usage: "Wait for the transaction to be included in a block", + } +) + +// SignAndSend adds network and system fees to the provided transaction and +// either sends it to the network (with a confirmation or --force flag) or saves +// it into a file (given in the --out flag). +func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *transaction.Transaction) error { + var ( + err error + gas = flags.Fixed8FromContext(ctx, "gas") + sysgas = flags.Fixed8FromContext(ctx, "sysgas") + ver = act.GetVersion() + aer *state.AppExecResult + ) + + tx.SystemFee += int64(sysgas) + tx.NetworkFee += int64(gas) + + if outFile := ctx.String("out"); outFile != "" { + // Make a long-lived transaction, it's to be signed manually. + tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2 + err = paramcontext.InitAndSave(ver.Protocol.Network, tx, acc, outFile) + } else { + if !ctx.Bool("force") { + promptTime := time.Now() + err := input.ConfirmTx(ctx.App.Writer, tx) + if err != nil { + return cli.Exit(err, 1) + } + waitTime := time.Since(promptTime) + // Compensate for confirmation waiting. + tx.ValidUntilBlock += uint32(waitTime.Milliseconds()/int64(ver.Protocol.MillisecondsPerBlock)) + 2 + } + var ( + resTx util.Uint256 + vub uint32 + ) + resTx, vub, err = act.SignAndSend(tx) + if err != nil { + return cli.Exit(err, 1) + } + if ctx.Bool("await") { + aer, err = act.Wait(ctx.Context, resTx, vub, err) + if err != nil { + return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1) + } + } + } + if err != nil { + return cli.Exit(err, 1) + } + + DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer) + return nil +} + +// DumpTransactionInfo prints transaction info to the given writer. +func DumpTransactionInfo(w io.Writer, h util.Uint256, res *state.AppExecResult) { + fmt.Fprintln(w, h.StringLE()) + if res != nil { + fmt.Fprintf(w, "OnChain:\t%t\n", res != nil) + fmt.Fprintf(w, "VMState:\t%s\n", res.VMState.String()) + if res.FaultException != "" { + fmt.Fprintf(w, "FaultException:\t%s\n", res.FaultException) + } + } +} diff --git a/cli/util/audit-bin.go b/cli/util/audit-bin.go new file mode 100644 index 0000000..87ccc90 --- /dev/null +++ b/cli/util/audit-bin.go @@ -0,0 +1,261 @@ +package util + +import ( + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/nspcc-dev/neofs-sdk-go/client" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + "github.com/nspcc-dev/neofs-sdk-go/object" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/urfave/cli/v2" +) + +func auditBin(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + var ( + numWorkers = ctx.Uint("workers") + + err error + errs = make(chan error, numWorkers) + haveErrors bool + tasks = make(chan func() error) + wg sync.WaitGroup + ) + for range numWorkers { + wg.Add(1) + go func() { + for f := range tasks { + err := f() + if err != nil { + errs <- err + break + } + } + wg.Done() + }() + } + + err = auditBinInt(ctx, tasks, errs) + close(tasks) + wg.Wait() + +drainErrors: + for { + select { + case anotherErr := <-errs: + fmt.Fprintf(ctx.App.Writer, "error in worker thread: %s\n", anotherErr) + haveErrors = true + default: + break drainErrors + } + } + if err == nil { + if haveErrors { + err = cli.Exit(errors.New("audit failed"), 1) // Change return code to signal thread errors. + } else { + fmt.Fprintln(ctx.App.Writer, "Audit is completed.") + } + } + return err +} + +func auditBinInt(ctx *cli.Context, tasks chan func() error, errs chan error) error { + retries := ctx.Uint("retries") + cnrID := ctx.String("container") + debug := ctx.Bool("debug") + dryRun := ctx.Bool("dry-run") + blockAttr := ctx.String("block-attribute") + curH := uint64(ctx.Uint("skip")) + + acc, _, err := options.GetAccFromContext(ctx) + if err != nil { + if errors.Is(err, options.ErrNoWallet) { + acc, err = wallet.NewAccount() + if err != nil { + return cli.Exit(fmt.Errorf("no wallet provided and failed to create account for NeoFS interaction: %w", err), 1) + } + } else { + return cli.Exit(fmt.Errorf("failed to load account: %w", err), 1) + } + } + signer, neoFSPool, err := options.GetNeoFSClientPool(ctx, acc) + if err != nil { + return cli.Exit(err, 1) + } + defer neoFSPool.Close() + + var containerID cid.ID + if err = containerID.DecodeString(cnrID); err != nil { + return cli.Exit(fmt.Errorf("failed to decode container ID: %w", err), 1) + } + if _, err = neoFSPool.ContainerGet(ctx.Context, containerID, client.PrmContainerGet{}); err != nil { + return cli.Exit(fmt.Errorf("failed to get container %s: %w", containerID, err), 1) + } + + if curH != 0 { + fmt.Fprintf(ctx.App.Writer, "Skipping first %d blocks\n", curH) + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + rpc, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return cli.Exit(fmt.Errorf("failed to create RPC client: %w", err), 1) + } + + var ( + prevH uint64 + cursor string + curOID oid.ID + f = object.NewSearchFilters() + ) + f.AddFilter(blockAttr, strconv.FormatUint(curH, 10), object.MatchNumGE) + + for { + var page []client.SearchResultItem + err = retry(func() error { + page, cursor, err = neoFSPool.SearchObjects(ctx.Context, containerID, f, []string{blockAttr}, cursor, signer, client.SearchObjectsOptions{}) + if err != nil { + return fmt.Errorf("failed to search objects: %w", err) + } + return nil + }, retries, debug) + if err != nil { + return cli.Exit(fmt.Errorf("search block objects: %w", err), 1) + } + + for _, itm := range page { + select { + case <-ctx.Done(): + return cli.Exit("context cancelled", 1) + case err := <-errs: + return cli.Exit(fmt.Errorf("error in worker thread: %w", err), 1) + default: + } + if len(itm.Attributes) != 1 { + fmt.Fprintf(ctx.App.Writer, "invalid number attributes for %s: expected %d, got %d", itm.ID, 1, len(itm.Attributes)) + continue + } + h, err := strconv.ParseUint(itm.Attributes[0], 10, 64) + if err != nil { + return cli.Exit(fmt.Errorf("failed to parse block OID (%s): %w", itm.ID, err), 1) + } + + if !curOID.IsZero() && prevH == h { + if dryRun { + fmt.Fprintf(ctx.App.Writer, "[dry-run] block duplicate %s / %s (%d)\n", itm.ID, curOID, prevH) + } else { + tasks <- wrapDropBlock(ctx, neoFSPool, signer, containerID, itm.ID, retries, prevH, curOID, debug) + } + continue + } + + for ; curH < h; curH++ { + if dryRun { + fmt.Fprintf(ctx.App.Writer, "[dry-run] block %d is missing\n", curH) + } else { + tasks <- wrapRestoreMissingBlock(ctx, rpc, neoFSPool, signer, containerID, blockAttr, retries, curH, debug) + } + } + curOID = itm.ID + prevH = curH + curH++ + } + if cursor == "" { + break + } + } + + return nil +} + +func wrapDropBlock(ctx *cli.Context, p *pool.Pool, signer user.Signer, containerID cid.ID, objID oid.ID, retries uint, prevH uint64, curOID oid.ID, debug bool) func() error { + return func() error { + return dropBlock(ctx, p, signer, containerID, objID, retries, prevH, curOID, debug) + } +} + +func dropBlock(ctx *cli.Context, p *pool.Pool, signer user.Signer, containerID cid.ID, objID oid.ID, retries uint, prevH uint64, curOID oid.ID, debug bool) error { + err := retry(func() error { + _, e := p.ObjectDelete(ctx.Context, containerID, objID, signer, client.PrmObjectDelete{}) + return e + }, retries, debug) + if err != nil { + err = fmt.Errorf("failed to remove block duplicate %s / %s (%d): %w", objID, curOID, prevH, err) + } else if debug { + fmt.Fprintf(ctx.App.Writer, "block duplicate %s for %d is removed (%s kept)\n", objID, prevH, curOID) + } + return err +} + +func wrapRestoreMissingBlock(ctx *cli.Context, rpc *rpcclient.Client, p *pool.Pool, signer user.Signer, containerID cid.ID, + blockAttr string, retries uint, index uint64, debug bool) func() error { + return func() error { + return restoreMissingBlock(ctx, rpc, p, signer, containerID, blockAttr, retries, index, debug) + } +} +func restoreMissingBlock(ctx *cli.Context, rpc *rpcclient.Client, p *pool.Pool, signer user.Signer, containerID cid.ID, + blockAttr string, retries uint, index uint64, debug bool) error { + var ( + b *block.Block + err error + ) + err = retry(func() error { + b, err = rpc.GetBlockByIndex(uint32(index)) + return err + }, retries, debug) + if err != nil { + return fmt.Errorf("failed to fetch block %d: %w", index, err) + } + + bw := io.NewBufBinWriter() + b.EncodeBinary(bw.BinWriter) + if bw.Err != nil { + return fmt.Errorf("failed to encode block %d: %w", index, bw.Err) + } + + _, err = createBlockAndUpload(ctx, p, signer, containerID, b, bw, blockAttr, retries, index, debug) + return err +} + +func createBlockAndUpload(ctx *cli.Context, p *pool.Pool, signer user.Signer, containerID cid.ID, b *block.Block, + bw *io.BufBinWriter, blockAttr string, retries uint, index uint64, debug bool) (oid.ID, error) { + attrs := []object.Attribute{ + object.NewAttribute(blockAttr, strconv.FormatUint(uint64(b.Index), 10)), + object.NewAttribute("Hash", b.Hash().StringLE()), + object.NewAttribute("PrevHash", b.PrevHash.StringLE()), + object.NewAttribute("BlockTime", strconv.FormatUint(b.Timestamp, 10)), + object.NewAttribute("Timestamp", strconv.FormatInt(time.Now().Unix(), 10)), + } + + var ( + objBytes = bw.Bytes() + OID oid.ID + ) + err := retry(func() error { + var e error + OID, e = uploadObj(ctx.Context, p, signer, containerID, objBytes, attrs) + return e + }, retries, debug) + if err != nil { + return oid.ID{}, fmt.Errorf("failed to upload block %d: %w", index, err) + } + if debug { + fmt.Fprintf(ctx.App.Writer, "block %d is uploaded: %s\n", index, OID) + } + return OID, nil +} diff --git a/cli/util/cancel.go b/cli/util/cancel.go new file mode 100644 index 0000000..eca0f5d --- /dev/null +++ b/cli/util/cancel.go @@ -0,0 +1,103 @@ +package util + +import ( + "errors" + "fmt" + "strings" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/cli/txctx" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/urfave/cli/v2" +) + +func cancelTx(ctx *cli.Context) error { + args := ctx.Args().Slice() + if len(args) == 0 { + return cli.Exit("transaction hash is missing", 1) + } else if len(args) > 1 { + return cli.Exit("only one transaction hash is accepted", 1) + } + + txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x")) + if err != nil { + return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1) + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + acc, w, err := options.GetAccFromContext(ctx) + if err != nil { + return cli.Exit(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1) + } + defer w.Close() + + signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry) + if err != nil { + return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1) + } + c, a, exitErr := options.GetRPCWithActor(gctx, ctx, signers) + if exitErr != nil { + return exitErr + } + + mainTx, _ := c.GetRawTransactionVerbose(txHash) + if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) { + return cli.Exit(fmt.Errorf("target transaction %s is accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1) + } + + if mainTx != nil && !mainTx.HasSigner(acc.ScriptHash()) { + return cli.Exit(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1) + } + + resHash, resVub, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error { + err := actor.DefaultCheckerModifier(r, t) + if err != nil { + return err + } + if mainTx != nil { + t.NetworkFee = max(t.NetworkFee, mainTx.NetworkFee+1) + } + t.NetworkFee += int64(flags.Fixed8FromContext(ctx, "gas")) + if mainTx != nil { + t.ValidUntilBlock = mainTx.ValidUntilBlock + } + return nil + }) + if err != nil { + return cli.Exit(fmt.Errorf("failed to send conflicting transaction: %w", err), 1) + } + var res *state.AppExecResult + if ctx.Bool("await") { + res, err = a.WaitAny(gctx, resVub, txHash, resHash) + if err != nil { + if errors.Is(err, waiter.ErrTxNotAccepted) { + if mainTx == nil { + return cli.Exit(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1) + } + fmt.Fprintf(ctx.App.Writer, "Neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of both target and conflicting transactions). Main transaction is not valid anymore, cancellation is successful\n", resVub) + return nil + } + return cli.Exit(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1) + } + if txHash.Equals(res.Container) { + tx, err := c.GetRawTransactionVerbose(txHash) + if err != nil { + return cli.Exit(fmt.Errorf("target transaction %s is accepted", txHash), 1) + } + return cli.Exit(fmt.Errorf("target transaction %s is accepted at block %s", txHash, tx.Blockhash.StringLE()), 1) + } + fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted") + } + txctx.DumpTransactionInfo(ctx.App.Writer, resHash, res) + return nil +} diff --git a/cli/util/convert.go b/cli/util/convert.go new file mode 100644 index 0000000..01475b6 --- /dev/null +++ b/cli/util/convert.go @@ -0,0 +1,275 @@ +package util + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "os" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/cli/txctx" + vmcli "github.com/nspcc-dev/neo-go/cli/vm" + "github.com/nspcc-dev/neo-go/pkg/services/helpers/neofs" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/urfave/cli/v2" +) + +var neoFSFlags = append([]cli.Flag{ + &cli.StringFlag{ + Name: "container", + Aliases: []string{"cid"}, + Usage: "NeoFS container ID to upload objects to", + Required: true, + Action: cmdargs.EnsureNotEmpty("container"), + }, + &flags.AddressFlag{ + Name: "address", + Usage: "Address to use for signing the uploading and searching transactions in NeoFS", + }, + &cli.UintFlag{ + Name: "retries", + Usage: "Maximum number of NeoFS node request retries", + Value: neofs.MaxRetries, + Action: func(context *cli.Context, u uint) error { + if u < 1 { + return cli.Exit("retries should be greater than 0", 1) + } + return nil + }, + }}, options.NeoFSRPC...) + +// NewCommands returns util commands for neo-go CLI. +func NewCommands() []*cli.Command { + // By default, RPC flag is required. sendtx and txdump may be called without provided rpc-endpoint. + rpcFlagOriginal, _ := options.RPC[0].(*cli.StringFlag) + rpcFlag := *rpcFlagOriginal + rpcFlag.Required = false + txDumpFlags := append([]cli.Flag{&rpcFlag}, options.RPC[1:]...) + txSendFlags := append(txDumpFlags, txctx.AwaitFlag) + txCancelFlags := append([]cli.Flag{ + &flags.AddressFlag{ + Name: "address", + Aliases: []string{"a"}, + Usage: "Address to use as conflicting transaction signee (and gas source)", + }, + txctx.GasFlag, + txctx.AwaitFlag, + }, options.RPC...) + txCancelFlags = append(txCancelFlags, options.Wallet...) + uploadBinFlags := append([]cli.Flag{ + &cli.StringFlag{ + Name: "block-attribute", + Usage: "Attribute key of the block object", + Value: neofs.DefaultBlockAttribute, + Action: cmdargs.EnsureNotEmpty("block-attribute"), + }, + &cli.UintFlag{ + Name: "batch-size", + Usage: "Size of the batch of blocks to upload in parallel. The batch size should be consistent from run to run, otherwise gaps in the uploaded blocks may occur.", + Value: neofs.DefaultBatchSize, + }, + &cli.UintFlag{ + Name: "workers", + Usage: "Number of workers to fetch and upload blocks concurrently", + Value: 20, + }, + options.Debug, + options.ForceTimestampLogs, + }, options.RPC...) + uploadBinFlags = append(uploadBinFlags, options.Wallet...) + uploadBinFlags = append(uploadBinFlags, neoFSFlags...) + + uploadStateFlags := append([]cli.Flag{ + &cli.StringFlag{ + Name: "state-attribute", + Usage: "Attribute key of the state object", + Value: neofs.DefaultStateAttribute, + Action: cmdargs.EnsureNotEmpty("state-attribute"), + }, + options.Debug, options.ForceTimestampLogs, options.Config, options.ConfigFile, options.RelativePath, + }, options.Wallet...) + uploadStateFlags = append(uploadStateFlags, options.Network...) + uploadStateFlags = append(uploadStateFlags, neoFSFlags...) + + auditBinFlags := append([]cli.Flag{ + &cli.StringFlag{ + Name: "block-attribute", + Usage: "Attribute key of the block object", + Value: neofs.DefaultBlockAttribute, + Action: cmdargs.EnsureNotEmpty("block-attribute"), + }, + &cli.BoolFlag{ + Name: "dry-run", + Usage: "If set, the command will not delete any objects, but will print the list of objects to be deleted", + Value: false, + }, + &cli.UintFlag{ + Name: "workers", + Usage: "Number of workers to delete and upload blocks concurrently", + Value: 20, + }, + &cli.IntFlag{ + Name: "skip", + Usage: "Number of blocks to skip audit for", + Value: 0, + Action: func(context *cli.Context, i int) error { + if i < 0 { + return cli.Exit("negative --skip", 1) + } + return nil + }, + }, + options.Debug, + options.ForceTimestampLogs, + }, neoFSFlags...) + auditBinFlags = append(auditBinFlags, options.Wallet...) + auditBinFlags = append(auditBinFlags, options.RPC...) + return []*cli.Command{ + { + Name: "util", + Usage: "Various helper commands", + Subcommands: []*cli.Command{ + { + Name: "convert", + Usage: "Convert provided argument into other possible formats", + UsageText: `convert + + is an argument which is tried to be interpreted as an item of different types + and converted to other formats. Strings are escaped and output in quotes.`, + Action: handleParse, + }, + { + Name: "sendtx", + Usage: "Send complete transaction stored in a context file", + UsageText: "sendtx [-r ] [--await] ", + Description: `Sends the transaction from the given context file to the given RPC node if it's + completely signed and ready. This command expects a ContractParametersContext + JSON file for input, it can't handle binary (or hex- or base64-encoded) + transactions. If the --await flag is included, the command waits for the + transaction to be included in a block before exiting. +`, + Action: sendTx, + Flags: txSendFlags, + }, + { + Name: "canceltx", + Usage: "Cancel transaction by sending conflicting transaction", + UsageText: "canceltx -r --wallet [--address ] [--wallet-config ] [--gas ] [--await] ", + Description: `Aims to prevent a transaction from being added to the blockchain by dispatching a more + prioritized conflicting transaction to the specified RPC node. The input for this command should + be the transaction hash. If another account is not specified, the conflicting transaction is + automatically generated and signed by the default account in the wallet. If the target transaction + is in the memory pool of the provided RPC node, the NetworkFee value of the conflicting transaction + is set to the target transaction's NetworkFee value plus one (if it's sufficient for the + conflicting transaction itself), the ValidUntilBlock value of the conflicting transaction is set to the + target transaction's ValidUntilBlock value. If the target transaction is not in the memory pool, standard + NetworkFee calculations are performed based on the calculatenetworkfee RPC request. If the --gas + flag is included, the specified value is added to the resulting conflicting transaction network fee + in both scenarios. When the --await flag is included, the command waits for one of the conflicting + or target transactions to be included in a block. +`, + Action: cancelTx, + Flags: txCancelFlags, + }, + { + Name: "txdump", + Usage: "Dump transaction stored in file", + UsageText: "txdump [-r ] ", + Action: txDump, + Flags: txDumpFlags, + Description: `Dumps the transaction from the given parameter context file to + the output. This command expects a ContractParametersContext JSON file for input, it can't handle + binary (or hex- or base64-encoded) transactions. If --rpc-endpoint flag is specified the result + of the given script after running it true the VM will be printed. Otherwise only transaction will + be printed. +`, + }, + { + Name: "ops", + Usage: "Pretty-print VM opcodes of the given base64- or hex- encoded script (base64 is checked first). If the input file is specified, then the script is taken from the file.", + UsageText: "ops [-i path-to-file] [--hex] ", + Action: handleOps, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Usage: "Input file containing base64- or hex- encoded script representation", + }, + &cli.BoolFlag{ + Name: "hex", + Usage: "Use hex encoding and do not check base64", + }, + }, + }, + { + Name: "upload-bin", + Usage: "Fetch blocks from RPC node and upload them to the NeoFS container", + UsageText: "neo-go util upload-bin --fs-rpc-endpoint [,[...]] --container --block-attribute block --rpc-endpoint [--timeout