From 738d1b0eea6c39b5f5b8020a699098561e3f8c8e Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Tue, 5 Aug 2025 14:50:24 +0000 Subject: [PATCH 01/25] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index d00491fd7e..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/config/version.go b/config/version.go index 67ee33ceb3..fb08d9c578 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 4 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 2 +const VersionMinor = 3 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From 0fd6a25e8d3e3759796a2e8716affa0b17162bbc Mon Sep 17 00:00:00 2001 From: nullun Date: Tue, 5 Aug 2025 16:29:04 +0100 Subject: [PATCH 02/25] node: print repeating error to the node log (#6403) --- go.mod | 2 +- node/node.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bf9dc4c21e..c3ccdf86d4 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,6 @@ require ( github.com/karalabe/hid v1.0.1-0.20240919124526-821c38d2678e github.com/klauspost/cpuid/v2 v2.2.8 github.com/labstack/echo/v4 v4.13.3 - github.com/labstack/gommon v0.4.2 github.com/libp2p/go-libp2p v0.37.0 github.com/libp2p/go-libp2p-kad-dht v0.28.0 github.com/libp2p/go-libp2p-kbucket v0.6.4 @@ -116,6 +115,7 @@ require ( github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.2.0 // indirect diff --git a/node/node.go b/node/node.go index 0782f595cf..be0491ec8a 100644 --- a/node/node.go +++ b/node/node.go @@ -29,7 +29,6 @@ import ( "time" "github.com/algorand/go-deadlock" - "github.com/labstack/gommon/log" "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/agreement/gossip" @@ -407,7 +406,7 @@ func (node *AlgorandFullNode) Start() error { return case <-ticker.C: // continue logging the error periodically - log.Errorf(node.hybridError) + node.log.Error(node.hybridError) } } }() From e11f501efe78cc958c0c124712f925eb008e6841 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:48:26 -0400 Subject: [PATCH 03/25] CI: some improvements for faster PR builds (#6397) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/actions/setup-go/action.yml | 55 +++ .github/actions/setup-test/action.yml | 40 ++ .github/actions/slack-notify/action.yml | 35 ++ .github/workflows/benchmarks.yml | 9 +- .../{build.yml => build-windows.yml} | 11 +- .github/workflows/ci-nightly.yml | 197 +++------- .github/workflows/ci-pr.yml | 345 ++++++------------ .github/workflows/codegen_verification.yml | 12 +- .github/workflows/reviewdog.yml | 23 +- .github/workflows/tools.yml | 16 +- AGENTS.md | 4 +- Makefile | 23 +- scripts/travis/before_build.sh | 10 +- scripts/travis/codegen_verification.sh | 10 +- test/scripts/e2e.sh | 15 +- test/scripts/e2e_client_runner.py | 2 +- test/scripts/e2e_go_tests.sh | 8 +- 17 files changed, 361 insertions(+), 454 deletions(-) create mode 100644 .github/actions/setup-go/action.yml create mode 100644 .github/actions/setup-test/action.yml create mode 100644 .github/actions/slack-notify/action.yml rename .github/workflows/{build.yml => build-windows.yml} (81%) diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml new file mode 100644 index 0000000000..a9eb59b6d5 --- /dev/null +++ b/.github/actions/setup-go/action.yml @@ -0,0 +1,55 @@ +name: 'Setup Go with caching' +description: 'Set up Go with mod and build caching' +inputs: + cache-prefix: + description: 'Cache prefix for advanced caching (e.g., "test", "build-e2e", "buildsrc")' + required: false + default: '' +runs: + using: 'composite' + steps: + - name: Get Go version + id: go_version + run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV + shell: bash + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: ${{ inputs.cache-prefix == '' }} + # branch-aware caching with fallback key prefixes following examples from: + # https://github.com/algorand/go-algorand/blob/ab03c94d2008dd349b761ef2b949e17b361996a3/.circleci/config.yml#L407-L439 + # https://danp.net/posts/github-actions-go-cache/ + - name: Set cache date and paths + if: inputs.cache-prefix != '' + shell: bash + run: | + echo "GO_CACHE_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + echo "GOMODCACHE=$(go env GOMODCACHE)" >> $GITHUB_ENV + echo "GOCACHE=$(go env GOCACHE)" >> $GITHUB_ENV + - name: Cache Go modules + if: inputs.cache-prefix != '' + uses: actions/cache@v4 + with: + path: ${{ env.GOMODCACHE }} + key: ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-mod-${{ github.ref_name }}-${{ hashFiles('**/go.sum') }}-${{ env.GO_CACHE_DATE }} + restore-keys: | + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-mod-${{ github.ref_name }}-${{ hashFiles('**/go.sum') }}- + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-mod-${{ github.ref_name }}- + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-mod-master- + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-mod- + - name: Cache Go build cache + if: inputs.cache-prefix != '' + uses: actions/cache@v4 + with: + path: ${{ env.GOCACHE }} + key: ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-build-${{ github.ref_name }}-${{ hashFiles('**/go.sum') }}-${{ env.GO_CACHE_DATE }} + restore-keys: | + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-build-${{ github.ref_name }}-${{ hashFiles('**/go.sum') }}- + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-build-${{ github.ref_name }}- + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-build-master- + ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ runner.arch }}-go-build- + - name: Download Go modules + if: inputs.cache-prefix != '' + shell: bash + run: go mod download diff --git a/.github/actions/setup-test/action.yml b/.github/actions/setup-test/action.yml new file mode 100644 index 0000000000..142fe8cc0a --- /dev/null +++ b/.github/actions/setup-test/action.yml @@ -0,0 +1,40 @@ +name: 'Set up Test Environment' +description: 'Set up testing environment for all platforms' +inputs: + install-expect: + description: 'Whether to install expect package' + required: false + default: 'false' +runs: + using: 'composite' + steps: + - name: Cache libsodium + uses: actions/cache@v4 + with: + path: crypto/libs + key: libsodium-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('crypto/libsodium-fork/**') }} + - name: Build libsodium + run: make libsodium + shell: bash + - name: Configure development environment + if: runner.os != 'Linux' + run: ./scripts/configure_dev.sh + shell: bash + # GHA Linux VMs provide all needed apt packages, except expect + - name: Install expect package + if: inputs.install-expect == 'true' && runner.os == 'Linux' + run: | + echo 'set man-db/auto-update false' | sudo debconf-communicate >/dev/null + sudo dpkg-reconfigure -f noninteractive man-db + sudo apt-get install -y --no-upgrade expect + shell: bash + - name: Cache gotestsum + id: cache-gotestsum + uses: actions/cache@v4 + with: + path: ~/go/bin/gotestsum + key: gotestsum-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/buildtools/versions') }} + - name: Install gotestsum + if: steps.cache-gotestsum.outputs.cache-hit != 'true' + run: ./scripts/buildtools/install_buildtools.sh -o "gotest.tools/gotestsum" + shell: bash diff --git a/.github/actions/slack-notify/action.yml b/.github/actions/slack-notify/action.yml new file mode 100644 index 0000000000..99dd5418d6 --- /dev/null +++ b/.github/actions/slack-notify/action.yml @@ -0,0 +1,35 @@ +name: 'Slack Failure Notification' +description: 'Send failure notification to Slack' +inputs: + job-type: + description: 'Type of job that failed (e.g., Test, Integration Test, etc.)' + required: true + build-type: + description: 'Type of build (e.g., PR Build, Nightly Build)' + required: true + details: + description: 'Additional job-specific details (formatted string)' + required: false + default: '' +runs: + using: 'composite' + steps: + - name: Send Slack notification + if: env.SLACK_WEBHOOK != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ env.SLACK_WEBHOOK }} + webhook-type: webhook-trigger + payload: | + { + "text": "🚨 ${{ inputs.job-type }} Failure Alert", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*${{ inputs.job-type }} Failure in ${{ inputs.build-type }}*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`${{ inputs.details != '' && format('\n{0}', inputs.details) || '' }}\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + } + ] + } \ No newline at end of file diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 0c6dbc2167..57d3469761 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -18,12 +18,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' + - name: Set up Go + uses: ./.github/actions/setup-go - run: go version - name: Build go-algorand - run: scripts/travis/build.sh + run: | + export SKIP_GO_INSTALLATION=True + scripts/travis/build.sh # BenchmarkUintMath - Serves as a proxy for AVM `eval` performance. # Performance degradations suggest either or both: (1) op code # degradation, (2) `eval` degradation. (2) suggests a broader performance diff --git a/.github/workflows/build.yml b/.github/workflows/build-windows.yml similarity index 81% rename from .github/workflows/build.yml rename to .github/workflows/build-windows.yml index 5b92352841..37de6ed9db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build-windows.yml @@ -20,19 +20,14 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Determine Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - - name: Install golang - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} + - name: Set up Go + uses: ./.github/actions/setup-go - name: Restore libsodium from cache id: cache-libsodium uses: actions/cache@v4 with: path: crypto/libs - key: libsodium-fork-v2-${{ runner.os }}-${{ hashFiles('crypto/libsodium-fork/**') }} + key: libsodium-fork-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('crypto/libsodium-fork/**') }} - name: Build run: | export ALGORAND_DEADLOCK=enable diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index 299c0b29ee..22d0d1de18 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -19,6 +19,7 @@ env: BUILD_TYPE: integration ALGOTEST: 1 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SKIP_GO_INSTALLATION: True concurrency: group: nightly-${{ github.ref }} @@ -41,14 +42,8 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} fetch-depth: 0 - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true + uses: ./.github/actions/setup-go - name: Cache libsodium uses: actions/cache@v4 with: @@ -68,24 +63,12 @@ jobs: path: /tmp/workspace-${{ matrix.platform }}.tar.gz retention-days: 1 - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Build Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Build Failure in Nightly Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Build" + build-type: "Nightly Build" + details: "• Platform: `${{ matrix.platform }}`" test_nightly: needs: [build] @@ -110,14 +93,8 @@ jobs: tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true + uses: ./.github/actions/setup-go - name: Run tests run: | ./scripts/configure_dev.sh @@ -125,31 +102,19 @@ jobs: PACKAGES="$(go list ./... | grep -v /go-algorand/test/)" export PACKAGE_NAMES=$(echo $PACKAGES | tr -d '\n') mkdir -p test_results/${{ matrix.platform }}_test_nightly/${PARTITION_ID} - gotestsum --format standard-verbose \ + gotestsum --format standard-quiet \ --junitfile ~/test_results/${{ matrix.platform }}_test_nightly/${PARTITION_ID}/results.xml \ --jsonfile ~/test_results/${{ matrix.platform }}_test_nightly/${PARTITION_ID}/testresults.json \ -- --tags "sqlite_unlock_notify sqlite_omit_load_extension" \ -race -timeout 1h -coverprofile=coverage.txt -covermode=atomic -p 1 \ $PACKAGE_NAMES - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Test Failure in Nightly Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Test" + build-type: "Nightly Build" + details: "• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_tests.name }}`" - name: Upload test artifacts to GitHub uses: actions/upload-artifact@v4 with: @@ -200,14 +165,8 @@ jobs: tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true + uses: ./.github/actions/setup-go - name: Run integration tests run: | ./scripts/configure_dev.sh @@ -216,24 +175,12 @@ jobs: TEST_RESULTS=~/test_results/${{ matrix.platform }}_integration_nightly/${PARTITION_ID} \ test/scripts/run_integration_tests.sh - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Integration Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Integration Test Failure in Nightly Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_integration_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Integration Test" + build-type: "Nightly Build" + details: "• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_integration_tests.name }}`" - name: Upload test artifacts to GitHub uses: actions/upload-artifact@v4 with: @@ -273,14 +220,8 @@ jobs: tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true + uses: ./.github/actions/setup-go - name: Run E2E expect tests run: | scripts/configure_dev.sh @@ -289,24 +230,12 @@ jobs: TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_expect_nightly/${PARTITION_ID} \ test/scripts/run_integration_tests.sh - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Expect Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Expect Test Failure in Nightly Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Expect Test" + build-type: "Nightly Build" + details: "• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`" - name: Upload test artifacts to GitHub uses: actions/upload-artifact@v4 with: @@ -344,14 +273,8 @@ jobs: tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true + uses: ./.github/actions/setup-go - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4.2.1 with: @@ -366,24 +289,12 @@ jobs: TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_subs_nightly \ test/scripts/run_integration_tests.sh - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Subs Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Subs Test Failure in Nightly Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Subs Test" + build-type: "Nightly Build" + details: "• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`" - name: Upload test artifacts to GitHub uses: actions/upload-artifact@v4 with: @@ -417,24 +328,12 @@ jobs: TestGoalWithExpect \ TestTealdbgWithExpect - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Verify Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Verify Failure in PR Build*\n\n• Job: `upload`\n• Branch: `${{ github.ref_name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Verify" + build-type: "Nightly Build" + details: "• Test Type: `${{ matrix.test_type }}`\n• Branch: `${{ github.ref_name }}`" upload: needs: [verify_nightly, e2e_subs_nightly] @@ -474,21 +373,9 @@ jobs: scripts/travis/deploy_packages.sh shell: bash - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Upload Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Upload Failure in Nightly Build*\n\n• Job: `upload`\n• Branch: `${{ github.ref_name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Upload" + build-type: "Nightly Build" + details: "• Branch: `${{ github.ref_name }}`" diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index df6161d5ad..3856e9a65b 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -12,6 +12,7 @@ env: BUILD_TYPE: integration ALGOTEST: 1 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SKIP_GO_INSTALLATION: True concurrency: group: pr-${{ github.event.pull_request.number || github.ref }} @@ -22,127 +23,49 @@ permissions: contents: read jobs: - build: - strategy: - matrix: - platform: ["ubuntu-24.04"] - runs-on: ${{ matrix.platform }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - name: Cache libsodium - uses: actions/cache@v4 - with: - path: crypto/libs - key: libsodium-${{ matrix.platform }}-${{ hashFiles('crypto/libsodium-fork/**') }} - - name: Build - run: | - scripts/travis/build.sh --make_debug - - name: Create workspace archive - run: | - tar -czf /tmp/workspace-${{ matrix.platform }}.tar.gz . - shell: bash - - name: Upload workspace archive - uses: actions/upload-artifact@v4 - with: - name: workspace-${{ matrix.platform }}-${{ github.run_id }} - path: /tmp/workspace-${{ matrix.platform }}.tar.gz - retention-days: 1 - - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 - with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Build Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Build Failure in PR Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } - test: - needs: [build] strategy: fail-fast: false matrix: platform: ["ubuntu-24.04"] - partition_id: [0, 1, 2, 3] # set PARTITION_TOTAL below to match + partition_id: [0, 1, 2, 3, 4, 5] # set PARTITION_TOTAL below to match runs-on: ${{ matrix.platform }} env: PARTITION_ID: ${{ matrix.partition_id }} - PARTITION_TOTAL: 4 + PARTITION_TOTAL: 6 CIRCLECI: true SHORTTEST: "-short" steps: - - name: Download workspace archive - uses: actions/download-artifact@v4 + - name: Checkout code + uses: actions/checkout@v4 with: - name: workspace-${{ matrix.platform }}-${{ github.run_id }} - path: /tmp/ - - name: Extract workspace archive - run: | - tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz - rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz - shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV + fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: ./.github/actions/setup-go with: - go-version: ${{ env.GO_VERSION }} - cache: true + cache-prefix: test + - name: Setup test environment + uses: ./.github/actions/setup-test - name: Run tests run: | - ./scripts/configure_dev.sh - ./scripts/buildtools/install_buildtools.sh -o "gotest.tools/gotestsum" PACKAGES="$(go list ./... | grep -v /go-algorand/test/)" export PACKAGE_NAMES=$(echo $PACKAGES | tr -d '\n') mkdir -p test_results/${{ matrix.platform }}_test/${PARTITION_ID} - gotestsum --format standard-verbose \ + gotestsum --format standard-quiet \ --junitfile ~/test_results/${{ matrix.platform }}_test/${PARTITION_ID}/results.xml \ --jsonfile ~/test_results/${{ matrix.platform }}_test/${PARTITION_ID}/testresults.json \ -- --tags "sqlite_unlock_notify sqlite_omit_load_extension" $SHORTTEST \ -race -timeout 1h -coverprofile=coverage.txt -covermode=atomic -p 4 \ $PACKAGE_NAMES - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Test Failure in PR Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Test" + build-type: "PR Build" + details: "• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_tests.name }}`" - name: Upload test artifacts to GitHub + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.platform }}-${{ github.run_id }}-${{ matrix.partition_id }} @@ -168,66 +91,46 @@ jobs: fail_ci_if_error: false integration: - needs: [build] strategy: fail-fast: false matrix: platform: ["ubuntu-24.04"] - partition_id: [0, 1, 2, 3] # set PARTITION_TOTAL below to match + partition_id: [0, 1, 2, 3, 4] # set PARTITION_TOTAL below to match runs-on: ${{ matrix.platform }} env: CIRCLECI: true PARTITION_ID: ${{ matrix.partition_id }} - PARTITION_TOTAL: 4 + PARTITION_TOTAL: 5 E2E_TEST_FILTER: GO PARALLEL_FLAG: "-p 4" SHORTTEST: "-short" steps: - - name: Download workspace archive - uses: actions/download-artifact@v4 + - name: Checkout code + uses: actions/checkout@v4 with: - name: workspace-${{ matrix.platform }}-${{ github.run_id }} - path: /tmp/ - - name: Extract workspace archive - run: | - tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz - rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz - shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV + fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: ./.github/actions/setup-go with: - go-version: ${{ env.GO_VERSION }} - cache: true + cache-prefix: build-e2e + - name: Setup test environment + uses: ./.github/actions/setup-test + - name: Build e2e binaries + run: make build-e2e - name: Run integration tests run: | - ./scripts/configure_dev.sh - ./scripts/buildtools/install_buildtools.sh -o "gotest.tools/gotestsum" mkdir -p ~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID} - TEST_RESULTS=~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID} \ - test/scripts/run_integration_tests.sh + NO_BUILD=true TEST_RESULTS=~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID} \ + test/scripts/e2e.sh - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Integration Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Integration Test Failure in PR Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_integration_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Integration Test" + build-type: "PR Build" + details: "• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_integration_tests.name }}`" - name: Upload test artifacts to GitHub + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: integration-results-${{ matrix.platform }}-${{ github.run_id }}-${{ matrix.partition_id }} @@ -242,66 +145,48 @@ jobs: fail_ci_if_error: false e2e_expect: - needs: [build] strategy: fail-fast: false matrix: platform: ["ubuntu-24.04"] - partition_id: [0, 1, 2, 3, 4, 5, 6, 7] # set PARTITION_TOTAL below to match + partition_id: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # set PARTITION_TOTAL below to match runs-on: ${{ matrix.platform }} env: CIRCLECI: true PARTITION_ID: ${{ matrix.partition_id }} - PARTITION_TOTAL: 8 + PARTITION_TOTAL: 10 E2E_TEST_FILTER: EXPECT PARALLEL_FLAG: "-p 4" SHORTTEST: "-short" steps: - - name: Download workspace archive - uses: actions/download-artifact@v4 + - name: Checkout code + uses: actions/checkout@v4 with: - name: workspace-${{ matrix.platform }}-${{ github.run_id }} - path: /tmp/ - - name: Extract workspace archive - run: | - tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz - rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz - shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV + fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: ./.github/actions/setup-go + with: + cache-prefix: build-e2e + - name: Setup test environment + uses: ./.github/actions/setup-test with: - go-version: ${{ env.GO_VERSION }} - cache: true + install-expect: true + - name: Build e2e binaries + run: make build-e2e - name: Run E2E expect tests run: | - scripts/configure_dev.sh - scripts/buildtools/install_buildtools.sh -o "gotest.tools/gotestsum" mkdir -p ~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID} - TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID} \ - test/scripts/run_integration_tests.sh + NO_BUILD=true TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID} \ + test/scripts/e2e.sh - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Expect Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Expect Test Failure in PR Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Expect Test" + build-type: "PR Build" + details: "• Partition: `${{ matrix.partition_id }}` of ${{ env.PARTITION_TOTAL }}\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`" - name: Upload test artifacts to GitHub + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: e2e_expect-results-${{ matrix.platform }}-${{ github.run_id }}-${{ matrix.partition_id }} @@ -316,82 +201,64 @@ jobs: fail_ci_if_error: false e2e_subs: - needs: [build] strategy: fail-fast: false matrix: platform: ["ubuntu-24.04"] + testsuite: ["parallel", "vdir-serial"] runs-on: ${{ matrix.platform }} env: E2E_TEST_FILTER: SCRIPTS + E2E_SUBS_TESTSUITE: ${{ matrix.testsuite }} CI_PLATFORM: ${{ matrix.platform }} CI_KEEP_TEMP_PLATFORM: "" SHORTTEST: "-short" steps: - - name: Download workspace archive - uses: actions/download-artifact@v4 + - name: Checkout code + uses: actions/checkout@v4 with: - name: workspace-${{ matrix.platform }}-${{ github.run_id }} - path: /tmp/ - - name: Extract workspace archive - run: | - tar -xzf /tmp/workspace-${{ matrix.platform }}.tar.gz - rm -f /tmp/workspace-${{ matrix.platform }}.tar.gz - shell: bash - - name: Get Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV + fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: ./.github/actions/setup-go with: - go-version: ${{ env.GO_VERSION }} - cache: true + cache-prefix: buildsrc + - name: Setup test environment + uses: ./.github/actions/setup-test + - name: Build binaries + run: make buildsrc - name: Run E2E subs tests run: | - scripts/configure_dev.sh - scripts/buildtools/install_buildtools.sh -o "gotest.tools/gotestsum" mkdir -p ~/test_results/${{ matrix.platform }}_e2e_subs - TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_subs \ - test/scripts/run_integration_tests.sh + NO_BUILD=true TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_subs \ + test/scripts/e2e.sh - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Subs Test Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Subs Test Failure in PR Build*\n\n• Job Type: `${{ github.job }}`\n• Platform: `${{ matrix.platform }}`\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Subs Test" + build-type: "PR Build" + details: "• Test Suite: `${{ matrix.testsuite }}`\n• Failed Step: `${{ steps.run_e2e_expect_tests.name }}`" - name: Upload test artifacts to GitHub + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: - name: e2e_subs-results-${{ matrix.platform }}-${{ github.run_id }} + name: e2e_subs-results-${{ matrix.platform }}-${{ github.run_id }}-${{ matrix.testsuite }} path: ~/test_results retention-days: 7 verify: - needs: [test, integration, e2e_expect] + needs: [test, integration, e2e_expect, e2e_subs] strategy: fail-fast: false matrix: - test_type: ["test", "integration", "e2e_expect"] + test_type: ["test", "integration", "e2e_expect", "e2e_subs"] platform: ["ubuntu-24.04"] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: - pattern: ${{ matrix.test_type }}-results-${{ matrix.platform }}-${{ github.run_id }}-* + pattern: ${{ matrix.test_type }}-results-${{ matrix.platform }}-* path: ~/test_results merge-multiple: true - name: Check test execution @@ -405,21 +272,39 @@ jobs: TestGoalWithExpect \ TestTealdbgWithExpect - name: Notify Slack on failure - if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + if: failure() + uses: ./.github/actions/slack-notify with: - webhook: ${{ secrets.SLACK_WEBHOOK }} - webhook-type: webhook-trigger - payload: | - { - "text": "🚨 Verify Failure Alert", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Verify Failure in PR Build*\n\n• Job: `upload`\n• Branch: `${{ github.ref_name }}`\n• Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - } - ] - } + job-type: "Verify" + build-type: "PR Build" + details: "• Test Type: `${{ matrix.test_type }}`\n• Branch: `${{ github.ref_name }}`" + + report: + needs: [test, integration, e2e_expect, e2e_subs] + if: always() + runs-on: ubuntu-24.04 + steps: + - uses: actions/download-artifact@v4 + with: + pattern: "*-results-*" + path: ~/test_results + merge-multiple: true + - name: Generate failure summary + run: | + echo "## Test Results" >> $GITHUB_STEP_SUMMARY + for job_type in test integration e2e_expect e2e_subs; do + FAILED_TESTS=$(find ~/test_results -path "*_${job_type}/*" -name "testresults.json" -exec jq -r 'select(.Action == "fail" and .Test) | "\(.Package)/\(.Test)"' {} \; 2>/dev/null | sort -u | grep -v '^$' || true) + if [ -n "$FAILED_TESTS" ]; then + echo "### ${job_type} failures" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$FAILED_TESTS" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "FAILED=true" >> $GITHUB_ENV + else + echo "### ${job_type} ✅" >> $GITHUB_STEP_SUMMARY + fi + done + + - name: Fail if there were test failures + if: env.FAILED == 'true' + run: exit 1 diff --git a/.github/workflows/codegen_verification.yml b/.github/workflows/codegen_verification.yml index 8dd82c5d31..d1b187b798 100644 --- a/.github/workflows/codegen_verification.yml +++ b/.github/workflows/codegen_verification.yml @@ -4,6 +4,9 @@ on: branches: - master pull_request: + +env: + SKIP_GO_INSTALLATION: True jobs: codegen_verification: runs-on: ubuntu-24.04 @@ -17,15 +20,14 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - path: go-algorand - - name: Uninstall existing go installation - run: sudo apt-get -y -q purge golang-go + - name: Set up Go + uses: ./.github/actions/setup-go + - name: Setup test environment + uses: ./.github/actions/setup-test - name: Run codegen_verification.sh env: SWAGGER_CONVERTER_API: "http://localhost:8080" run: | - export GOPATH="${GITHUB_WORKSPACE}/go" - cd go-algorand scripts/travis/codegen_verification.sh - name: Slack Notification env: diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index 6725e1642d..81f5564487 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -15,9 +15,14 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 # required for new-from-rev option in .golangci.yml + - name: Cache libsodium + uses: actions/cache@v4 + with: + path: crypto/libs + key: libsodium-ubuntu-latest-${{ hashFiles('crypto/libsodium-fork/**') }} # move go out of the way temporarily to avoid "go list ./..." from installing modules - name: Make libsodium.a - run: sudo mv /usr/bin/go /usr/bin/go.bak && make crypto/libs/linux/amd64/lib/libsodium.a && sudo mv /usr/bin/go.bak /usr/bin/go + run: sudo mv /usr/bin/go /usr/bin/go.bak && make libsodium && sudo mv /usr/bin/go.bak /usr/bin/go - name: reviewdog-golangci-lint uses: reviewdog/action-golangci-lint@v2.7.0 with: @@ -37,20 +42,20 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 # required for new-from-rev option in .golangci.yml + - name: Cache libsodium + uses: actions/cache@v4 + with: + path: crypto/libs + key: libsodium-ubuntu-latest-${{ hashFiles('crypto/libsodium-fork/**') }} # move go out of the way temporarily to avoid "go list ./..." from installing modules - name: Make libsodium.a - run: sudo mv /usr/bin/go /usr/bin/go.bak && make crypto/libs/linux/amd64/lib/libsodium.a && sudo mv /usr/bin/go.bak /usr/bin/go + run: sudo mv /usr/bin/go /usr/bin/go.bak && make libsodium && sudo mv /usr/bin/go.bak /usr/bin/go - name: Add bin to PATH run: | echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH - - name: Determine Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - - name: Install specific golang - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} + - name: Set up Go + uses: ./.github/actions/setup-go - name: Create folders for golangci-lint run: mkdir -p cicdtmp/golangci-lint - name: Check if custom golangci-lint is already built diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 16fcbaa372..cfb1b964f2 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -17,20 +17,20 @@ jobs: steps: - name: Check out code into the Go module directory uses: actions/checkout@v4 + - name: Cache libsodium + uses: actions/cache@v4 + with: + path: crypto/libs + key: libsodium-ubuntu-latest-${{ hashFiles('crypto/libsodium-fork/**') }} # move go out of the way temporarily to avoid "go list ./..." from installing modules - name: Make libsodium.a - run: sudo mv /usr/bin/go /usr/bin/go.bak && make crypto/libs/linux/amd64/lib/libsodium.a && sudo mv /usr/bin/go.bak /usr/bin/go + run: sudo mv /usr/bin/go /usr/bin/go.bak && make libsodium && sudo mv /usr/bin/go.bak /usr/bin/go - name: Add bin to PATH run: | echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH - - name: Determine Go version - id: go_version - run: echo "GO_VERSION=$(./scripts/get_golang_version.sh)" >> $GITHUB_ENV - - name: Install go version - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} + - name: Set up Go + uses: ./.github/actions/setup-go - name: Test tools/block-generator run: | cd tools/block-generator diff --git a/AGENTS.md b/AGENTS.md index 83b7eecdd2..1496e4276b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# CLAUDE.md +# AGENTS.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to coding agents when working with code in this repository. ## Common Development Commands diff --git a/Makefile b/Makefile index 2b16198b6b..76f4e27ce4 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,6 @@ BUILDBRANCH := $(shell ./scripts/compute_branch.sh) CHANNEL ?= $(shell ./scripts/compute_branch_channel.sh $(BUILDBRANCH)) DEFAULTNETWORK ?= $(shell ./scripts/compute_branch_network.sh $(BUILDBRANCH)) DEFAULT_DEADLOCK ?= $(shell ./scripts/compute_branch_deadlock_default.sh $(BUILDBRANCH)) -export GOCACHE=$(SRCPATH)/tmp/go-cache GOTAGSLIST := sqlite_unlock_notify sqlite_omit_load_extension @@ -167,6 +166,8 @@ logic: ALWAYS: # build our fork of libsodium, placing artifacts into crypto/lib/ and crypto/include/ +libsodium: crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a + crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a: mkdir -p crypto/copies/$(OS_TYPE)/$(ARCH) cp -R crypto/libsodium-fork/. crypto/copies/$(OS_TYPE)/$(ARCH)/libsodium-fork @@ -274,15 +275,8 @@ rebuild_kmd_swagger: deps build: buildsrc buildsrc-special -# We're making an empty file in the go-cache dir to -# get around a bug in go build where it will fail -# to cache binaries from time to time on empty NFS -# dirs -${GOCACHE}/file.txt: - mkdir -p "${GOCACHE}" - touch "${GOCACHE}"/file.txt -buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_exporter NONGO_BIN ${GOCACHE}/file.txt +buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_exporter NONGO_BIN $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... buildsrc-special: @@ -301,6 +295,15 @@ build-race: build GOBIN=$(GOBIN)-race go install $(GOTRIMPATH) $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" ./... cp $(GOBIN)/kmd $(GOBIN)-race +# Build binaries needed for e2e/integration tests +build-e2e: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a + @mkdir -p $(GOBIN)-race + # Build regular binaries (kmd, algod, goal) and race binaries in parallel + $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./cmd/kmd ./cmd/algod ./cmd/goal & \ + GOBIN=$(GOBIN)-race go install $(GOTRIMPATH) $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" ./cmd/goal ./cmd/algod ./cmd/algoh ./cmd/tealdbg ./cmd/msgpacktool ./cmd/algokey ./tools/teal/algotmpl ./test/e2e-go/cli/tealdbg/cdtmock & \ + wait + cp $(GOBIN)/kmd $(GOBIN)-race + NONGO_BIN_FILES=$(GOBIN)/find-nodes.sh $(GOBIN)/update.sh $(GOBIN)/COPYING $(GOBIN)/ddconfig.sh NONGO_BIN: $(NONGO_BIN_FILES) @@ -410,7 +413,7 @@ dump: $(addprefix gen/,$(addsuffix /genesis.dump, $(NETWORKS))) install: build scripts/dev_install.sh -p $(GOBIN) -.PHONY: default fmt lint check_shell sanity cover prof deps build test fulltest shorttest clean cleango deploy node_exporter install %gen gen NONGO_BIN check-go-version rebuild_kmd_swagger universal +.PHONY: default fmt lint check_shell sanity cover prof deps build build-race build-e2e test fulltest shorttest clean cleango deploy node_exporter install %gen gen NONGO_BIN check-go-version rebuild_kmd_swagger universal libsodium ###### TARGETS FOR CICD PROCESS ###### include ./scripts/release/mule/Makefile.mule diff --git a/scripts/travis/before_build.sh b/scripts/travis/before_build.sh index 3dbb7339d3..26988ac7c0 100755 --- a/scripts/travis/before_build.sh +++ b/scripts/travis/before_build.sh @@ -13,13 +13,7 @@ set -e GOPATH=$(go env GOPATH) export GOPATH -SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" -OS=$("${SCRIPTPATH}"/../ostype.sh) -ARCH=$("${SCRIPTPATH}"/../archtype.sh) - -if [ ! -f crypto/libs/${OS}/${ARCH}/lib/libsodium.a ]; then - echo "Building libsodium-fork..." - make crypto/libs/${OS}/${ARCH}/lib/libsodium.a -fi +echo "Building libsodium-fork if needed..." +make libsodium diff --git a/scripts/travis/codegen_verification.sh b/scripts/travis/codegen_verification.sh index 5e3a53de3b..934379b5b8 100755 --- a/scripts/travis/codegen_verification.sh +++ b/scripts/travis/codegen_verification.sh @@ -11,17 +11,14 @@ set -e ALGORAND_DEADLOCK=enable export ALGORAND_DEADLOCK +GOPATH=$(go env GOPATH) +export GOPATH SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" # Force re-evaluation of genesis files to see if source files changed w/o running make touch gen/generate.go -"${SCRIPTPATH}/build.sh" - -# Get the go build version. -GOLANG_VERSION=$(./scripts/get_golang_version.sh) - -eval "$(~/gimme "${GOLANG_VERSION}")" +make build "${SCRIPTPATH}"/../buildtools/install_buildtools.sh @@ -37,7 +34,6 @@ echo "Regenerate for stringer et el." make generate echo "Running fixcheck" -GOPATH=$(go env GOPATH) "$GOPATH"/bin/algofix -error */ echo "Running expect linter" diff --git a/test/scripts/e2e.sh b/test/scripts/e2e.sh index c1ecc2a289..fd5ac3ce04 100755 --- a/test/scripts/e2e.sh +++ b/test/scripts/e2e.sh @@ -10,7 +10,6 @@ export ALGOTEST=1 S3_TESTDATA=${S3_TESTDATA:-algorand-testdata} SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" - SRCROOT="$(pwd -P)" export CHANNEL=master @@ -26,7 +25,7 @@ Options: -n Run tests without building binaries (Binaries are expected in PATH) -i Start an interactive session for running e2e subs. " -NO_BUILD=false +NO_BUILD=${NO_BUILD:-false} while getopts ":c:nhi" opt; do case ${opt} in c ) CHANNEL=$OPTARG @@ -193,7 +192,10 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then echo "done" exit else - $clientrunner ${KEEP_TEMPS_CMD_STR} "$SRCROOT"/test/scripts/e2e_subs/*.{sh,py} + # Run parallel tests if testsuite is unset or set to "parallel" + if [ "${E2E_SUBS_TESTSUITE}" = "" ] || [ "${E2E_SUBS_TESTSUITE}" = "parallel" ]; then + $clientrunner ${KEEP_TEMPS_CMD_STR} "$SRCROOT"/test/scripts/e2e_subs/*.{sh,py} + fi fi # If the temporary artifact directory exists, then the test artifact needs to be created @@ -214,6 +216,9 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then duration "parallel client runner" + # Run vdir and serial tests if testsuite is unset or set to "vdir-serial" + if [ "${E2E_SUBS_TESTSUITE}" = "" ] || [ "${E2E_SUBS_TESTSUITE}" = "vdir-serial" ]; then + for vdir in "$SRCROOT"/test/scripts/e2e_subs/v??; do $clientrunner --version "$(basename "$vdir")" "$vdir"/*.sh done @@ -223,8 +228,10 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then $clientrunner "$script" done - deactivate duration "serial client runners" + fi # if E2E_SUBS_TESTSUITE == "" or == "vdir-serial" + deactivate + fi # if E2E_TEST_FILTER == "" or == "SCRIPTS" if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "GO" ]; then diff --git a/test/scripts/e2e_client_runner.py b/test/scripts/e2e_client_runner.py index 2ff7f876cc..d024233793 100755 --- a/test/scripts/e2e_client_runner.py +++ b/test/scripts/e2e_client_runner.py @@ -483,7 +483,7 @@ def main(): trdir = os.path.join(trdir, package) os.makedirs(trdir, exist_ok=True) - jsonpath = os.path.join(trdir, "results.json") + jsonpath = os.path.join(trdir, "testresults.json") rs.jsonfile = open(jsonpath, "w") junitpath = os.path.join(trdir, "testresults.xml") atexit.register(finish_test_results, rs.jsonfile, jsonpath, junitpath) diff --git a/test/scripts/e2e_go_tests.sh b/test/scripts/e2e_go_tests.sh index 1d2810f60a..66165c39b5 100755 --- a/test/scripts/e2e_go_tests.sh +++ b/test/scripts/e2e_go_tests.sh @@ -56,9 +56,11 @@ REPO_ROOT="$( cd "$(dirname "$0")" ; pwd -P )"/../.. if [ "${NORACEBUILD}" = "" ]; then # Need bin-race binaries for e2e tests - pushd ${REPO_ROOT} - make build-race -j4 - popd + if [ "${NO_BUILD}" != "true" ]; then + pushd ${REPO_ROOT} + make build-e2e + popd + fi RACE_OPTION="-race" else RACE_OPTION="" From 1d39d701603cb9de3cfc969f5728b5eb61c12401 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:42:30 -0400 Subject: [PATCH 04/25] tests: network flaky tests fixes (#6407) Co-authored-by: cce <51567+cce@users.noreply.github.com> --- network/p2pNetwork_test.go | 2 +- network/wsNetwork_test.go | 2 +- node/node_test.go | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index dd2bad274b..50c907f091 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -1250,7 +1250,7 @@ func TestP2PwsStreamHandlerDedup(t *testing.T) { defer netB.Stop() require.Eventually(t, func() bool { - return networkPeerIdentityDisconnect.GetUint64Value() == networkPeerIdentityDisconnectInitial+1 + return networkPeerIdentityDisconnect.GetUint64Value() > networkPeerIdentityDisconnectInitial }, 2*time.Second, 50*time.Millisecond) // now allow the peer made outgoing connection to handle conn closing initiated by the other side diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index a2a6bec1ce..07da18f59e 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -337,7 +337,7 @@ func setupWebsocketNetworkABwithLogger(t *testing.T, countTarget int, log loggin counter := newMessageCounter(t, countTarget) netB.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counter}}) - readyTimeout := time.NewTimer(2 * time.Second) + readyTimeout := time.NewTimer(5 * time.Second) waitReady(t, netA, readyTimeout.C) t.Log("a ready") waitReady(t, netB, readyTimeout.C) diff --git a/node/node_test.go b/node/node_test.go index 93e0d10428..1cd3c46cb0 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -952,10 +952,18 @@ func TestNodeHybridTopology(t *testing.T) { startAndConnectNodes(nodes, 10*time.Second) // ensure the initial connectivity topology + repeatCounter := 0 require.Eventually(t, func() bool { + repeatCounter++ node0Conn := len(nodes[0].net.GetPeers(network.PeersConnectedIn)) > 0 // has connection from 1 node1Conn := len(nodes[1].net.GetPeers(network.PeersConnectedOut, network.PeersConnectedIn)) == 2 // connected to 0 and 2 node2Conn := len(nodes[2].net.GetPeers(network.PeersConnectedOut, network.PeersConnectedIn)) >= 1 // connected to 1 + if repeatCounter > 100 && !(node0Conn && node1Conn && node2Conn) { + t.Logf("IN/OUT connection stats:\nNode0 %d/%d, Node1 %d/%d, Node2 %d/%d", + len(nodes[0].net.GetPeers(network.PeersConnectedIn)), len(nodes[0].net.GetPeers(network.PeersConnectedOut)), + len(nodes[1].net.GetPeers(network.PeersConnectedIn)), len(nodes[1].net.GetPeers(network.PeersConnectedOut)), + len(nodes[2].net.GetPeers(network.PeersConnectedIn)), len(nodes[2].net.GetPeers(network.PeersConnectedOut))) + } return node0Conn && node1Conn && node2Conn }, 60*time.Second, 500*time.Millisecond) From f086a8c886ecb16ed076f40d95538006438453b2 Mon Sep 17 00:00:00 2001 From: nullun Date: Tue, 5 Aug 2025 21:59:04 +0100 Subject: [PATCH 05/25] goal: minor text fix (#6404) --- cmd/goal/multisig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/multisig.go b/cmd/goal/multisig.go index 3b85ba6ac2..c0e5ad9bbe 100644 --- a/cmd/goal/multisig.go +++ b/cmd/goal/multisig.go @@ -142,7 +142,7 @@ var addSigCmd = &cobra.Command{ } var signProgramCmd = &cobra.Command{ - Use: "signprogram -t [transaction file] -a [address]", + Use: "signprogram -a [address]", Short: "Add a signature to a multisig LogicSig", Long: `Start a multisig LogicSig, or add a signature to an existing multisig, for a given program.`, Args: validateNoPosArgsFn, From 5991e1ab6f91fc0b77829f0d314a573c9050cc27 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:59:43 -0400 Subject: [PATCH 06/25] network: fix mesher stopping logic (#6406) --- network/hybridNetwork.go | 3 ++- network/mesh.go | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index 57ef3bd078..1f2545b149 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -212,12 +212,13 @@ func (n *HybridP2PNetwork) Start() error { // Stop implements GossipNode func (n *HybridP2PNetwork) Stop() { + n.mesher.stop() + _ = n.runParallel(func(net GossipNode) error { net.Stop() return nil }) - n.mesher.stop() } // RegisterHandlers adds to the set of given message handlers. diff --git a/network/mesh.go b/network/mesh.go index 7b020c610a..6a1cdfb6d2 100644 --- a/network/mesh.go +++ b/network/mesh.go @@ -35,12 +35,14 @@ type mesher interface { } type baseMesher struct { - wg sync.WaitGroup + wg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc meshConfig } type meshConfig struct { - ctx context.Context + parentCtx context.Context meshUpdateRequests chan meshRequest meshThreadInterval time.Duration backoff backoff.BackoffStrategy @@ -96,7 +98,7 @@ func withMeshUpdateInterval(d time.Duration) meshOption { func withContext(ctx context.Context) meshOption { return func(cfg *meshConfig) { - cfg.ctx = ctx + cfg.parentCtx = ctx } } @@ -117,7 +119,7 @@ func newBaseMesher(opts ...meshOption) (*baseMesher, error) { for _, opt := range opts { opt(&cfg) } - if cfg.ctx == nil { + if cfg.parentCtx == nil { return nil, errors.New("context is not set") } if cfg.netMeshFn == nil { @@ -130,7 +132,10 @@ func newBaseMesher(opts ...meshOption) (*baseMesher, error) { cfg.meshThreadInterval = meshThreadInterval } + ctx, cancel := context.WithCancel(cfg.parentCtx) return &baseMesher{ + ctx: ctx, + cancel: cancel, meshConfig: cfg, }, nil } @@ -178,6 +183,7 @@ func (m *baseMesher) start() { } func (m *baseMesher) stop() { + m.cancel() m.wg.Wait() if m.closer != nil { m.closer() From 0f61d1d143779dc5ca5ef09488bb0f0f7f84bd23 Mon Sep 17 00:00:00 2001 From: nullun Date: Wed, 6 Aug 2025 15:37:24 +0100 Subject: [PATCH 07/25] goal: add '--txid' flag for displaying TxID with 'goal clerk inspect' (#6401) --- cmd/goal/clerk.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 2112a5f736..cec1701955 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -68,6 +68,7 @@ var ( rawOutput bool requestFilename string requestOutFilename string + inspectTxid bool simulateStartRound basics.Round simulateAllowEmptySignatures bool @@ -98,6 +99,9 @@ func init() { // Wallet to be used for the clerk operation clerkCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation") + // inspect flags + inspectCmd.Flags().BoolVarP(&inspectTxid, "txid", "t", false, "Display the TxID for each transaction") + // send flags sendCmd.Flags().StringVarP(&account, "from", "f", "", "Account address to send the money from (If not specified, uses default account)") sendCmd.Flags().StringVarP(&toAddress, "to", "t", "", "Address to send to money to (required)") @@ -723,7 +727,11 @@ var inspectCmd = &cobra.Command{ if err != nil { reportErrorf(txDecodeError, txFilename, err) } - fmt.Printf("%s[%d]\n%s\n\n", txFilename, count, string(protocol.EncodeJSON(sti))) + if inspectTxid { + fmt.Printf("%s[%d] - %s\n%s\n\n", txFilename, count, sti.Txn.ID(), string(protocol.EncodeJSON(sti))) + } else { + fmt.Printf("%s[%d]\n%s\n\n", txFilename, count, string(protocol.EncodeJSON(sti))) + } count++ } } From dd597f62c45d786c59840d0d2a5563c771414dac Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:49:30 -0400 Subject: [PATCH 08/25] CI: build Go cache on master merges (#6408) --- .github/workflows/ci-nightly.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index 22d0d1de18..c4ad34fc96 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -44,6 +44,8 @@ jobs: fetch-depth: 0 - name: Set up Go uses: ./.github/actions/setup-go + with: + cache-prefix: buildsrc - name: Cache libsodium uses: actions/cache@v4 with: @@ -95,6 +97,8 @@ jobs: shell: bash - name: Set up Go uses: ./.github/actions/setup-go + with: + cache-prefix: test - name: Run tests run: | ./scripts/configure_dev.sh @@ -167,6 +171,8 @@ jobs: shell: bash - name: Set up Go uses: ./.github/actions/setup-go + with: + cache-prefix: build-e2e - name: Run integration tests run: | ./scripts/configure_dev.sh @@ -222,6 +228,8 @@ jobs: shell: bash - name: Set up Go uses: ./.github/actions/setup-go + with: + cache-prefix: build-e2e - name: Run E2E expect tests run: | scripts/configure_dev.sh @@ -275,6 +283,8 @@ jobs: shell: bash - name: Set up Go uses: ./.github/actions/setup-go + with: + cache-prefix: buildsrc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4.2.1 with: From af7e67a0f43896cb3ed4e9e14fc609b31e78dff3 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 6 Aug 2025 10:53:03 -0400 Subject: [PATCH 09/25] Chore: Remove an unneeded consensus flag to simplify work on big programs (#6392) --- config/consensus.go | 10 ++++---- data/transactions/application.go | 36 ++++++++++++++++----------- data/transactions/application_test.go | 16 ------------ ledger/apply/application.go | 33 +++++++----------------- ledger/apply/application_test.go | 8 +++--- 5 files changed, 38 insertions(+), 65 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index 2f081fc936..042b0f78ca 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -448,9 +448,9 @@ type ConsensusParams struct { // 6. checking that in the case of going online the VoteFirst is less or equal to the next network round. EnableKeyregCoherencyCheck bool - // Allow app updates to specify the extra pages they use. This allows the - // update to pass WellFormed(), but they cannot _change_ the extra pages. - EnableExtraPagesOnAppUpdate bool + // When extra pages were introduced, a bug prevented the extra pages of an + // app from being properly removed from the creator upon deletion. + EnableProperExtraPageAccounting bool // Autoincrements an app's version when the app is updated, careful callers // may avoid making inner calls to apps that have changed. @@ -1179,8 +1179,8 @@ func initConsensusProtocols() { v29 := v28 v29.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // Enable ExtraProgramPages for application update - v29.EnableExtraPagesOnAppUpdate = true + // Fix the accounting bug + v29.EnableProperExtraPageAccounting = true Consensus[protocol.ConsensusV29] = v29 diff --git a/data/transactions/application.go b/data/transactions/application.go index db43130aad..3e200d9f1f 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -271,10 +271,7 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro return fmt.Errorf("tx.ExtraProgramPages is immutable") } - if proto.EnableExtraPagesOnAppUpdate { - effectiveEPP = uint32(proto.MaxExtraAppProgramPages) - } - + effectiveEPP = uint32(proto.MaxExtraAppProgramPages) } // Limit total number of arguments @@ -320,17 +317,8 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro return fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", proto.MaxExtraAppProgramPages) } - lap := len(ac.ApprovalProgram) - lcs := len(ac.ClearStateProgram) - pages := int(1 + effectiveEPP) - if lap > pages*proto.MaxAppProgramLen { - return fmt.Errorf("approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen) - } - if lcs > pages*proto.MaxAppProgramLen { - return fmt.Errorf("clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen) - } - if lap+lcs > pages*proto.MaxAppTotalProgramLen { - return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen) + if err := ac.WellSizedPrograms(effectiveEPP, proto); err != nil { + return err } for i, br := range ac.Boxes { @@ -354,6 +342,24 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro return nil } +// WellSizedPrograms checks the sizes of the programs in ac, based on the +// parameters of proto and returns an error if they are too big. +func (ac ApplicationCallTxnFields) WellSizedPrograms(extraPages uint32, proto config.ConsensusParams) error { + lap := len(ac.ApprovalProgram) + lcs := len(ac.ClearStateProgram) + pages := int(1 + extraPages) + if lap > pages*proto.MaxAppProgramLen { + return fmt.Errorf("approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen) + } + if lcs > pages*proto.MaxAppProgramLen { + return fmt.Errorf("clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen) + } + if lap+lcs > pages*proto.MaxAppTotalProgramLen { + return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen) + } + return nil +} + // AddressByIndex converts an integer index into an address associated with the // transaction. Index 0 corresponds to the transaction sender, and an index > 0 // corresponds to an offset into txn.Accounts. Returns an error if the index is diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index 3f7f7e8d17..ae36fac2f8 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -340,7 +340,6 @@ func TestAppCallCreateWellFormed(t *testing.T) { func TestWellFormedErrors(t *testing.T) { partitiontest.PartitionTest(t) - curProto := config.Consensus[protocol.ConsensusCurrentVersion] futureProto := config.Consensus[protocol.ConsensusFuture] protoV27 := config.Consensus[protocol.ConsensusV27] protoV28 := config.Consensus[protocol.ConsensusV28] @@ -520,21 +519,6 @@ func TestWellFormedErrors(t *testing.T) { proto: futureProto, expectedError: fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = 8"), }, - { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ApprovalProgram: []byte(strings.Repeat("X", 1025)), - ClearStateProgram: []byte(strings.Repeat("X", 1025)), - ExtraProgramPages: 0, - OnCompletion: UpdateApplicationOC, - }, - }, - proto: protoV28, - expectedError: fmt.Errorf("app programs too long. max total len %d bytes", curProto.MaxAppProgramLen), - }, { tx: Transaction{ Type: protocol.ApplicationCallTx, diff --git a/ledger/apply/application.go b/ledger/apply/application.go index 397c43d68e..8e65329b89 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -156,15 +156,9 @@ func deleteApplication(balances Balances, creator basics.Address, appIdx basics. record.TotalAppSchema = totalSchema record.TotalAppParams = basics.SubSaturate(record.TotalAppParams, 1) - // Delete app's extra program pages - totalExtraPages := record.TotalExtraAppPages - if totalExtraPages > 0 { - proto := balances.ConsensusParams() - if proto.EnableExtraPagesOnAppUpdate { - extraPages := params.ExtraProgramPages - totalExtraPages = basics.SubSaturate(totalExtraPages, extraPages) - } - record.TotalExtraAppPages = totalExtraPages + // There was a short-lived bug so in one version, pages were not deallocated. + if balances.ConsensusParams().EnableProperExtraPageAccounting { + record.TotalExtraAppPages = basics.SubSaturate(record.TotalExtraAppPages, params.ExtraProgramPages) } err = balances.Put(creator, record) @@ -194,22 +188,13 @@ func updateApplication(ac *transactions.ApplicationCallTxnFields, balances Balan return err } - // Fill in the new programs proto := balances.ConsensusParams() - // when proto.EnableExtraPageOnAppUpdate is false, WellFormed rejects all updates with a multiple-page program - if proto.EnableExtraPagesOnAppUpdate { - lap := len(ac.ApprovalProgram) - lcs := len(ac.ClearStateProgram) - pages := int(1 + params.ExtraProgramPages) - if lap > pages*proto.MaxAppProgramLen { - return fmt.Errorf("updateApplication approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen) - } - if lcs > pages*proto.MaxAppProgramLen { - return fmt.Errorf("updateApplication clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen) - } - if lap+lcs > pages*proto.MaxAppTotalProgramLen { - return fmt.Errorf("updateApplication app programs too long, %d. max total len %d bytes", lap+lcs, pages*proto.MaxAppTotalProgramLen) - } + + // The pre-application well-formedness check rejects big programs + // conservatively, it doesn't know the actual params.ExtraProgramPages, so + // it allows any programs that fit under the absolute max. + if err = ac.WellSizedPrograms(params.ExtraProgramPages, proto); err != nil { + return err } params.ApprovalProgram = ac.ApprovalProgram diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index a11578bd59..f570a4b665 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -719,8 +719,7 @@ func TestAppCallOptIn(t *testing.T) { appIdx++ err = optInApplication(b, sender, appIdx, aparams) if cparams.MaxAppsOptedIn > 0 { - a.Error(err) - a.Contains(err.Error(), "max opted-in apps per acct") + a.ErrorContains(err, "max opted-in apps per acct") } else { a.NoError(err) } @@ -1110,8 +1109,7 @@ func TestAppCallApplyUpdate(t *testing.T) { b.pass = true err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), fmt.Sprintf("updateApplication %s program too long", test.name)) + a.ErrorContains(err, fmt.Sprintf("%s program too long", test.name)) } b.ResetWrites() @@ -1137,7 +1135,7 @@ func TestAppCallApplyUpdate(t *testing.T) { } b.pass = true err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) - a.ErrorContains(err, "updateApplication app programs too long") + a.ErrorContains(err, "app programs too long") } From 4a6519d7c2d29b5b0d17bb385860d82101e47b62 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 6 Aug 2025 13:49:31 -0400 Subject: [PATCH 10/25] Build: .gitignore .claude (#6409) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5515ceed59..b553f63bce 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ tools/x-repo-types/x-repo-types # ignore local claude config changes CLAUDE.local.md +.claude From be17b0e81d5f46bc5d95b586b6b07751acd4eaed Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 6 Aug 2025 14:05:54 -0400 Subject: [PATCH 11/25] Apps: txn.Access list for access to more resources (#6286) Co-authored-by: Gary Malouf <982483+gmalouf@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: cce <51567+cce@users.noreply.github.com> --- cmd/goal/account.go | 6 +- cmd/goal/application.go | 363 +- cmd/goal/application_test.go | 29 + cmd/goal/asset.go | 24 +- cmd/goal/clerk.go | 29 +- cmd/goal/commands.go | 8 +- cmd/goal/formatting_test.go | 55 +- cmd/goal/interact.go | 25 +- config/consensus.go | 20 +- daemon/algod/api/server/v2/handlers.go | 3 +- daemon/algod/api/server/v2/utils.go | 33 +- data/basics/address.go | 2 +- data/basics/testing/nearzero.go | 187 + data/basics/testing/nearzero_test.go | 140 + data/basics/userBalance.go | 21 + data/transactions/application.go | 313 +- data/transactions/application_test.go | 917 +++-- data/transactions/logic/README.md | 99 +- data/transactions/logic/README_in.md | 99 +- data/transactions/logic/box.go | 4 +- data/transactions/logic/box_test.go | 299 +- data/transactions/logic/eval.go | 126 +- data/transactions/logic/evalStateful_test.go | 659 ++-- data/transactions/logic/eval_test.go | 12 +- data/transactions/logic/export_test.go | 3 + data/transactions/logic/resources.go | 90 +- data/transactions/logic/resources_test.go | 245 +- data/transactions/msgp_gen.go | 3227 +++++++++++------ data/transactions/msgp_gen_test.go | 180 + data/txntest/txn.go | 6 + ledger/apply/application.go | 8 +- ledger/apply/application_test.go | 136 +- ledger/apptxn_test.go | 4 +- ledger/boxtxn_test.go | 46 +- ledger/simulation/resources.go | 8 +- ledger/simulation/simulation_eval_test.go | 70 +- libgoal/libgoal_test.go | 238 ++ libgoal/transactions.go | 195 +- scripts/export_sdk_types.py | 23 +- shared/pingpong/accounts.go | 4 +- shared/pingpong/pingpong.go | 39 +- .../features/accountPerf/sixMillion_test.go | 6 +- .../features/transactions/accountv2_test.go | 12 +- .../features/transactions/app_pages_test.go | 11 +- .../features/transactions/application_test.go | 3 +- test/e2e-go/restAPI/other/appsRestAPI_test.go | 25 +- .../restAPI/simulate/simulateRestAPI_test.go | 49 +- .../upgrades/application_support_test.go | 9 +- test/scripts/e2e_subs/app-assets-access.sh | 332 ++ test/scripts/e2e_subs/app-assets.sh | 14 +- test/scripts/e2e_subs/e2e-app-simulate.sh | 2 +- test/scripts/e2e_subs/e2e-app-x-app-reads.sh | 36 +- test/scripts/e2e_subs/shared-resources.py | 2 +- .../e2e_subs/tealprogs/assets-escrow9.teal | 340 ++ .../scripts/e2e_subs/tealprogs/xappreads.teal | 19 +- util/fn.go | 54 + 56 files changed, 6387 insertions(+), 2522 deletions(-) create mode 100644 data/basics/testing/nearzero.go create mode 100644 data/basics/testing/nearzero_test.go create mode 100755 test/scripts/e2e_subs/app-assets-access.sh create mode 100644 test/scripts/e2e_subs/tealprogs/assets-escrow9.teal create mode 100644 util/fn.go diff --git a/cmd/goal/account.go b/cmd/goal/account.go index ca8da712a7..b7c4188c35 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -923,14 +923,14 @@ var changeOnlineCmd = &cobra.Command{ firstTxRound, lastTxRound, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } err = changeAccountOnlineStatus( accountAddress, online, statusChangeTxFile, walletName, firstTxRound, lastTxRound, transactionFee, scLeaseBytes(cmd), dataDir, client, ) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } }, } @@ -1099,7 +1099,7 @@ var renewParticipationKeyCmd = &cobra.Command{ err = generateAndRegisterPartKey(accountAddress, currentRound, roundLastValid, txRoundLastValid, transactionFee, scLeaseBytes(cmd), keyDilution, walletName, dataDir, client) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } version := config.GetCurrentVersion() diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 13ddd32d6b..3a3e931747 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -27,6 +27,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/spf13/cobra" @@ -40,6 +41,7 @@ import ( "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" ) var ( @@ -72,9 +74,16 @@ var ( // platform seems not so far-fetched? foreignApps []string foreignAssets []string - appBoxes []string // parse these as we do app args, with optional number and comma in front + appStrBoxes []string // parse these as we do app args, with optional number and comma in front appStrAccounts []string + // for these, an omitted addr is the sender. an omitted app is the called app. + appStrHoldings []string // format: asset+addr OR asset ex: 5245+XQJEJECPWUOXSKMIC5TCSARPVGHQJIIOKHO7WTKEPPLJMKG3D7VWWID66E + appStrLocals []string // format: app+addr OR app OR addr + + // controls whether all these refs put into the old-style "foreign arrays" or the new-style tx.Access + appUseAccess bool + appArgs []string appInputFilename string @@ -100,9 +109,12 @@ func init() { appCmd.PersistentFlags().StringArrayVar(&appArgs, "app-arg", nil, "Args to encode for application transactions (all will be encoded to a byte slice). For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.") appCmd.PersistentFlags().StringSliceVar(&foreignApps, "foreign-app", nil, "Indexes of other apps whose global state is read in this transaction") appCmd.PersistentFlags().StringSliceVar(&foreignAssets, "foreign-asset", nil, "Indexes of assets whose parameters are read in this transaction") - appCmd.PersistentFlags().StringArrayVar(&appBoxes, "box", nil, "Boxes that may be accessed by this transaction. Use the same form as app-arg to name the box, preceded by an optional app-id and comma. No app-id indicates the box is accessible by the app being called.") + appCmd.PersistentFlags().StringArrayVar(&appStrBoxes, "box", nil, "A Box that may be accessed by this transaction. Use the same form as app-arg to name the box, preceded by an optional app-id and comma. Zero or omitted app-id indicates the box is accessible by the app being called.") appCmd.PersistentFlags().StringSliceVar(&appStrAccounts, "app-account", nil, "Accounts that may be accessed from application logic") - appCmd.PersistentFlags().StringVarP(&appInputFilename, "app-input", "i", "", "JSON file containing encoded arguments and inputs (mutually exclusive with app-arg, app-account, foreign-app, foreign-asset, and box)") + appCmd.PersistentFlags().StringSliceVar(&appStrHoldings, "holding", nil, "A Holding that may be accessed from application logic. An asset-id followed by a comma and an address") + appCmd.PersistentFlags().StringSliceVar(&appStrLocals, "local", nil, "A Local State that may be accessed from application logic. An optional app-id and comma, followed by an address. Zero or omitted app-id indicates the local state for app being called.") + appCmd.PersistentFlags().BoolVar(&appUseAccess, "access", false, "Put references into the transaction's access list, instead of foreign arrays.") + appCmd.PersistentFlags().StringVarP(&appInputFilename, "app-input", "i", "", "JSON file containing encoded arguments and inputs (mutually exclusive with app-arg, app-account, foreign-app, foreign-asset, local, holding, and box)") appCmd.PersistentFlags().StringVar(&approvalProgFile, "approval-prog", "", "(Uncompiled) TEAL assembly program filename for approving/rejecting transactions") appCmd.PersistentFlags().StringVar(&clearProgFile, "clear-prog", "", "(Uncompiled) TEAL assembly program filename for updating application state when a user clears their local state") @@ -202,7 +214,7 @@ func panicIfErr(err error) { func newAppCallBytes(arg string) apps.AppCallBytes { appBytes, err := apps.NewAppCallBytes(arg) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } return appBytes } @@ -212,6 +224,9 @@ type appCallInputs struct { ForeignApps []uint64 `codec:"foreignapps"` ForeignAssets []uint64 `codec:"foreignassets"` Boxes []boxRef `codec:"boxes"` + Holdings []holdingRef `codec:"holdings"` + Locals []localRef `codec:"locals"` + UseAccess bool `codec:"access"` Args []apps.AppCallBytes `codec:"args"` } @@ -220,9 +235,35 @@ type boxRef struct { name apps.AppCallBytes `codec:"name"` } -// newBoxRef parses a command-line box ref, which is an optional appId, a comma, +type holdingRef struct { + assetID uint64 `codec:"asset"` + address string `codec:"account"` +} + +type localRef struct { + appID uint64 `codec:"app"` + address string `codec:"account"` +} + +// parseUInt64 parses a string into a uint64. It must succeed or the error is +// reprted and `goal` exits. It accepts extra arguments to create a more +// helpful error message. +func parseUInt64(number string, thing string, context ...string) uint64 { + n, err := strconv.ParseUint(number, 10, 64) + if err != nil { + extra := "" + if len(context) == 1 { + extra = " in " + context[0] + } + reportErrorf("Could not parse '%s' as %s%s: %s", + number, thing, extra, errors.Unwrap(err)) + } + return n +} + +// parseBoxRef parses a command-line box ref, which is an optional appId, a comma, // and then the same format as an app call arg. -func newBoxRef(arg string) boxRef { +func parseBoxRef(arg string) boxRef { encoding, value, found := strings.Cut(arg, ":") if !found { reportErrorf("box refs should be of the form '[,]encoding:value'") @@ -232,11 +273,7 @@ func newBoxRef(arg string) boxRef { if appStr, enc, found := strings.Cut(encoding, ","); found { // There was a comma in the part before the ":" encoding = enc - var err error - appID, err = strconv.ParseUint(appStr, 10, 64) - if err != nil { - reportErrorf("Could not parse app id in box ref: %v", err) - } + appID = parseUInt64(appStr, "app id", "box ref") } return boxRef{ appID: appID, @@ -244,86 +281,118 @@ func newBoxRef(arg string) boxRef { } } -func stringsToUint64(strs []string) []uint64 { - out := make([]uint64, len(strs)) - for i, idstr := range strs { - parsed, err := strconv.ParseUint(idstr, 10, 64) - if err != nil { - reportErrorf("Could not parse foreign app id: %v", err) - } - out[i] = parsed - } - return out -} +// parseHoldingRef parses a command-line box ref, which is an assetId and an +// optional address, separated by a plus sign. No address means Sender. +func parseHoldingRef(arg string) holdingRef { + assetStr, address, _ := strings.Cut(arg, "+") + assetID := parseUInt64(assetStr, "asset id", "holding ref") -func stringsToBoxRefs(strs []string) []boxRef { - out := make([]boxRef, len(strs)) - for i, brstr := range strs { - out[i] = newBoxRef(brstr) + return holdingRef{ + assetID: assetID, + address: address, // "" would mean Sender } - return out } -func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRef { - output := make([]transactions.BoxRef, len(input)) - for i, tbr := range input { - rawName, err := tbr.name.Raw() - if err != nil { - reportErrorf("Could not decode box name %s: %v", tbr.name, err) - } +// parseLocalRef parses a command-line local state ref, which is an optional appId +// and an optional address, separated by a plus sign. No appId means the called app, +// No address means Sender. They can not _both_ be omitted, as that is a +// non-sensical LocalRef - it would make the local state of the sender for the +// current app available. That is implicitly available already. +func parseLocalRef(arg string) localRef { + one, two, both := strings.Cut(arg, "+") - index := uint64(0) - if tbr.appID != 0 { - found := false - for a, id := range foreignApps { - if tbr.appID == id { - index = uint64(a + 1) - found = true - break - } - } - // Check appIdx after the foreignApps check. If the user actually - // put the appIdx in foreignApps, and then used the appIdx here - // (rather than 0), then maybe they really want to use it in the - // transaction as the full number. Though it's hard to see why. - if !found && tbr.appID == uint64(appIdx) { - index = 0 - found = true - } - if !found { - reportErrorf("Box ref with appId (%d) not in foreign-apps", tbr.appID) - } + if both { + appID := parseUInt64(one, "app id", "local ref") + return localRef{ + appID: appID, + address: two, } - output[i] = transactions.BoxRef{ - Index: index, - Name: rawName, + } + + // one is missing, so we should have a number or an address. Try to parse + // it as a number. If it fails, assume an address, because at this stage we + // don't parse addresses. + if appID, err := strconv.ParseUint(one, 10, 64); err == nil { + return localRef{ + appID: appID, + address: "", } } - return output + + return localRef{ + appID: 0, + address: one, + } } -func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) { - accounts = inputs.Accounts - foreignApps = inputs.ForeignApps - foreignAssets = inputs.ForeignAssets - boxes = translateBoxRefs(inputs.Boxes, foreignApps) - args = make([][]byte, len(inputs.Args)) - for i, arg := range inputs.Args { +// parseAppInputs converts inputs from a very textual input form (coming from +// CLI or a JSON file), to a more strongly typed form, using the various "real" +// types from `basics`. +func parseAppInputs(inputs appCallInputs) ([][]byte, libgoal.RefBundle) { + args := make([][]byte, 0, len(inputs.Args)) + for _, arg := range inputs.Args { rawValue, err := arg.Raw() if err != nil { - reportErrorf("Could not decode input at index %d: %v", i, err) + reportErrorf("Could not decode app-arg %s:%s: %v", arg.Encoding, arg.Value, err) } - args[i] = rawValue + args = append(args, rawValue) } - return + locals := util.Map(inputs.Locals, func(hr localRef) basics.LocalRef { + return basics.LocalRef{ + App: basics.AppIndex(hr.appID), + Address: cliAddress(hr.address)} + }) + holdings := util.Map(inputs.Holdings, func(hr holdingRef) basics.HoldingRef { + return basics.HoldingRef{ + Asset: basics.AssetIndex(hr.assetID), + Address: cliAddress(hr.address)} + }) + boxes := util.Map(inputs.Boxes, func(br boxRef) basics.BoxRef { + rawName, err := br.name.Raw() + if err != nil { + reportErrorf("Could not decode box name %s: %v", br.name, err) + } + return basics.BoxRef{App: basics.AppIndex(br.appID), Name: string(rawName)} + }) + refs := libgoal.RefBundle{ + UseAccess: inputs.UseAccess, + Accounts: util.Map(inputs.Accounts, cliAddress), + Apps: util.Map(inputs.ForeignApps, func(idx uint64) basics.AppIndex { return basics.AppIndex(idx) }), + Assets: util.Map(inputs.ForeignAssets, func(idx uint64) basics.AssetIndex { return basics.AssetIndex(idx) }), + + Locals: locals, + Holdings: holdings, + Boxes: boxes, + } + return args, refs +} + +func cliAddress(acct string) basics.Address { + if acct == "" { + return basics.Address{} // will be interpreted as Sender + } + if strings.HasPrefix(acct, "app(") && strings.HasSuffix(acct, ")") { + appStr := acct[4 : len(acct)-1] + appID := parseUInt64(appStr, "app id", acct) + return basics.AppIndex(appID).Address() + } + addr, err := basics.UnmarshalChecksumAddress(acct) + if err != nil { + reportErrorln(err) + } + return addr } -func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) { +func getAppInputsFromFile() appCallInputs { + reportWarnf("Using a JSON app input file is deprecated and will be removed soon. Please speak up if the feature matters to you.") + time.Sleep(5 * time.Second) + var inputs appCallInputs f, err := os.Open(appInputFilename) if err != nil { reportErrorf("Could not open app input JSON file: %v", err) } + defer f.Close() dec := protocol.NewJSONDecoder(f) err = dec.Decode(&inputs) @@ -331,22 +400,14 @@ func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint reportErrorf("Could not decode app input JSON file: %v", err) } - return parseAppInputs(inputs) + return inputs } -func getAppInputs() (args [][]byte, accounts []string, _ []uint64, assets []uint64, boxes []transactions.BoxRef) { - if appInputFilename != "" { - if appArgs != nil || appStrAccounts != nil || foreignApps != nil || foreignAssets != nil { - reportErrorf("Cannot specify both command-line arguments/resources and JSON input filename") - } - return processAppInputFile() - } - +func getAppInputsFromCLI() appCallInputs { // we need to ignore empty strings from appArgs because app-arg was // previously a StringSliceVar, which also does that, and some test depend // on it. appArgs became `StringArrayVar` in order to support abi arguments // which contain commas. - var encodedArgs []apps.AppCallBytes for _, arg := range appArgs { if len(arg) > 0 { @@ -354,12 +415,33 @@ func getAppInputs() (args [][]byte, accounts []string, _ []uint64, assets []uint } } - inputs := appCallInputs{ - Accounts: appStrAccounts, - ForeignApps: stringsToUint64(foreignApps), - ForeignAssets: stringsToUint64(foreignAssets), - Boxes: stringsToBoxRefs(appBoxes), - Args: encodedArgs, + return appCallInputs{ + UseAccess: appUseAccess, + Accounts: appStrAccounts, + ForeignApps: util.Map(foreignApps, func(s string) uint64 { + return parseUInt64(s, "app id", "foreign-app") + }), + ForeignAssets: util.Map(foreignAssets, func(s string) uint64 { + return parseUInt64(s, "asset id", "foreign-asset") + }), + Boxes: util.Map(appStrBoxes, parseBoxRef), + Holdings: util.Map(appStrHoldings, parseHoldingRef), + Locals: util.Map(appStrLocals, parseLocalRef), + Args: encodedArgs, + } +} + +func getAppInputs() ([][]byte, libgoal.RefBundle) { + var inputs appCallInputs + if appInputFilename != "" { + if appArgs != nil || appStrAccounts != nil || + foreignApps != nil || foreignAssets != nil || appStrBoxes != nil || + appStrHoldings != nil || appStrLocals != nil { + reportErrorf("Cannot specify both command-line arguments/resources and JSON input filename") + } + inputs = getAppInputsFromFile() + } else { + inputs = getAppInputsFromCLI() } return parseAppInputs(inputs) @@ -447,14 +529,15 @@ var createAppCmd = &cobra.Command{ // Parse transaction parameters approvalProg, clearProg := mustParseProgArgs() onCompletionEnum := mustParseOnCompletion(onCompletion) - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() switch onCompletionEnum { case transactions.CloseOutOC, transactions.ClearStateOC: reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", onCompletion) } - tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, boxes, extraPages) + tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, + appArgs, refs, extraPages) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -497,7 +580,7 @@ var createAppCmd = &cobra.Command{ if !noWaitAfterSend { txn, err1 := waitForCommit(client, txid, lv) if err1 != nil { - reportErrorln(err1.Error()) + reportErrorln(err1) } if txn.ApplicationIndex != nil && *txn.ApplicationIndex != 0 { reportInfof("Created app with app index %d", *txn.ApplicationIndex) @@ -511,7 +594,7 @@ var createAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -527,9 +610,9 @@ var updateAppCmd = &cobra.Command{ // Parse transaction parameters approvalProg, clearProg := mustParseProgArgs() - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() - tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, approvalProg, clearProg, rejectVersion) + tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, approvalProg, clearProg, refs, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -572,7 +655,7 @@ var updateAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lv) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { @@ -582,7 +665,7 @@ var updateAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -597,9 +680,9 @@ var optInAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() - tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion) + tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, refs, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -642,7 +725,7 @@ var optInAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lv) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { @@ -652,7 +735,7 @@ var optInAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -667,9 +750,9 @@ var closeOutAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() - tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion) + tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, refs, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -712,7 +795,7 @@ var closeOutAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lv) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { @@ -722,7 +805,7 @@ var closeOutAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -737,9 +820,9 @@ var clearAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() - tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion) + tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, refs, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -782,7 +865,7 @@ var clearAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lv) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { @@ -792,7 +875,7 @@ var clearAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -804,12 +887,11 @@ var callAppCmd = &cobra.Command{ Long: `Call an application, invoking application-specific functionality`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, _ []string) { - dataDir, client := getDataDirAndClient() - // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() + dataDir, client := getDataDirAndClient() - tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion) + tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, refs, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -852,7 +934,7 @@ var callAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lv) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { @@ -862,7 +944,7 @@ var callAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -877,9 +959,9 @@ var deleteAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgs, refs := getAppInputs() - tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion) + tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, refs, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -922,7 +1004,7 @@ var deleteAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lv) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { @@ -932,7 +1014,7 @@ var deleteAppCmd = &cobra.Command{ err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -1103,7 +1185,7 @@ func populateMethodCallTxnArgs(types []string, values []string) ([]transactions. // into the appropriate foreign array. Their placement will be as compact as possible, which means // values will be deduplicated and any value that is the sender or the current app will not be added // to the foreign array. -func populateMethodCallReferenceArgs(sender string, currentApp basics.AppIndex, types []string, values []string, accounts *[]string, apps *[]uint64, assets *[]uint64) ([]int, error) { +func populateMethodCallReferenceArgs(sender string, currentApp basics.AppIndex, types []string, values []string, refs *libgoal.RefBundle) ([]int, error) { resolvedIndexes := make([]int, len(types)) for i, value := range values { @@ -1114,29 +1196,28 @@ func populateMethodCallReferenceArgs(sender string, currentApp basics.AppIndex, if value == sender { resolved = 0 } else { + valAddress := cliAddress(value) duplicate := false - for j, account := range *accounts { - if value == account { + for j, account := range refs.Accounts { + if valAddress == account { resolved = j + 1 // + 1 because 0 is the sender duplicate = true break } } if !duplicate { - resolved = len(*accounts) + 1 - *accounts = append(*accounts, value) + resolved = len(refs.Accounts) + 1 + refs.Accounts = append(refs.Accounts, valAddress) } } case abi.ApplicationReferenceType: - appID, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return nil, fmt.Errorf("Unable to parse application ID '%s': %s", value, err) - } - if appID == uint64(currentApp) { + ui := parseUInt64(value, "app id") + appID := basics.AppIndex(ui) + if appID == currentApp { resolved = 0 } else { duplicate := false - for j, app := range *apps { + for j, app := range refs.Apps { if appID == app { resolved = j + 1 // + 1 because 0 is the current app duplicate = true @@ -1144,17 +1225,15 @@ func populateMethodCallReferenceArgs(sender string, currentApp basics.AppIndex, } } if !duplicate { - resolved = len(*apps) + 1 - *apps = append(*apps, appID) + resolved = len(refs.Apps) + 1 + refs.Apps = append(refs.Apps, appID) } } case abi.AssetReferenceType: - assetID, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return nil, fmt.Errorf("Unable to parse asset ID '%s': %s", value, err) - } + ui := parseUInt64(value, "asset id") + assetID := basics.AssetIndex(ui) duplicate := false - for j, asset := range *assets { + for j, asset := range refs.Assets { if assetID == asset { resolved = j duplicate = true @@ -1162,8 +1241,8 @@ func populateMethodCallReferenceArgs(sender string, currentApp basics.AppIndex, } } if !duplicate { - resolved = len(*assets) - *assets = append(*assets, assetID) + resolved = len(refs.Assets) + refs.Assets = append(refs.Assets, assetID) } default: return nil, fmt.Errorf("Unknown reference type: %s", types[i]) @@ -1254,7 +1333,7 @@ var methodAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgsParsed, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() + appArgsParsed, refs := getAppInputs() if len(appArgsParsed) > 0 { reportErrorf("--arg and --app-arg are mutually exclusive, do not use --app-arg") } @@ -1354,7 +1433,7 @@ var methodAppCmd = &cobra.Command{ } } - refArgsResolved, err := populateMethodCallReferenceArgs(account, appIdx, refArgTypes, refArgValues, &appAccounts, &foreignApps, &foreignAssets) + refArgsResolved, err := populateMethodCallReferenceArgs(account, basics.AppIndex(appIdx), refArgTypes, refArgValues, &refs) if err != nil { reportErrorf("error populating reference arguments: %v", err) } @@ -1375,7 +1454,7 @@ var methodAppCmd = &cobra.Command{ } appCallTxn, err := client.MakeUnsignedApplicationCallTx( - appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, boxes, + appIdx, applicationArgs, refs, onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, extraPages, rejectVersion) if err != nil { @@ -1452,7 +1531,7 @@ var methodAppCmd = &cobra.Command{ err = writeSignedTxnsToFile(signedTxnGroup, outFilename) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } return } @@ -1482,12 +1561,12 @@ var methodAppCmd = &cobra.Command{ if !noWaitAfterSend { _, err := waitForCommit(client, txid, lv) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } resp, err := client.PendingTransactionInformation(txid) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } if methodCreatesApp && resp.ApplicationIndex != nil && *resp.ApplicationIndex != 0 { diff --git a/cmd/goal/application_test.go b/cmd/goal/application_test.go index 6d72195621..2f614af558 100644 --- a/cmd/goal/application_test.go +++ b/cmd/goal/application_test.go @@ -21,6 +21,7 @@ import ( "slices" "testing" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -136,3 +137,31 @@ func TestParseMethodArgJSONtoByteSlice(t *testing.T) { }) } } + +func TestCliAddress(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := require.New(t) + + type testCase struct { + address string + valid bool + value basics.Address + } + tests := []testCase{ + {"", true, basics.Address{}}, + {"invalid", false, basics.Address{}}, + {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ", true, basics.Address{}}, + {"app(10)", true, basics.AppIndex(10).Address()}, + {basics.Address{0x07}.String(), true, basics.Address{0x07}}, + } + + for _, tc := range tests { + if tc.valid { + value := cliAddress(tc.address) + a.Equal(tc.value, value) + } else { + a.Panics(func() { cliAddress(tc.address) }) + } + } +} diff --git a/cmd/goal/asset.go b/cmd/goal/asset.go index f487d0f028..70802e03e3 100644 --- a/cmd/goal/asset.go +++ b/cmd/goal/asset.go @@ -323,7 +323,7 @@ var createAssetCmd = &cobra.Command{ if !noWaitAfterSend { txn, err1 := waitForCommit(client, txid, lv) if err1 != nil { - reportErrorln(err1.Error()) + reportErrorln(err1) } if txn.AssetIndex != nil && *txn.AssetIndex != 0 { reportInfof("Created asset with asset index %d", *txn.AssetIndex) @@ -332,7 +332,7 @@ var createAssetCmd = &cobra.Command{ } else { err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -402,13 +402,13 @@ var destroyAssetCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lastValid) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -495,13 +495,13 @@ var configAssetCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lastValid) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -582,13 +582,13 @@ var sendAssetCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lastValid) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -651,13 +651,13 @@ var freezeAssetCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lastValid) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -740,13 +740,13 @@ var optinAssetCmd = &cobra.Command{ if !noWaitAfterSend { _, err2 = waitForCommit(client, txid, lastValid) if err2 != nil { - reportErrorln(err2.Error()) + reportErrorln(err2) } } } else { err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index cec1701955..df4fbbd589 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -40,6 +40,7 @@ import ( "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" "github.com/spf13/cobra" ) @@ -415,13 +416,13 @@ var sendCmd = &cobra.Command{ var err1 error rekeyTo, err1 = basics.UnmarshalChecksumAddress(rekeyToAddress) if err1 != nil { - reportErrorln(err1.Error()) + reportErrorln(err1) } } client := ensureFullClient(dataDir) firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } payment, err := client.ConstructPayment( fromAddressResolved, toAddressResolved, fee, amount, noteBytes, closeToAddressResolved, @@ -552,7 +553,7 @@ var sendCmd = &cobra.Command{ if !noWaitAfterSend { _, err1 = waitForCommit(client, txid, lastValid) if err1 != nil { - reportErrorln(err1.Error()) + reportErrorln(err1) } } } else { @@ -562,7 +563,7 @@ var sendCmd = &cobra.Command{ err = writeFile(outFilename, protocol.Encode(&stx), 0600) } if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -1164,13 +1165,10 @@ var dryrunCmd = &cobra.Command{ // Write dryrun data to file dataDir := datadir.EnsureSingleDataDir() client := ensureFullClient(dataDir) - accts, err := unmarshalSlice(dumpForDryrunAccts) - if err != nil { - reportErrorln(err.Error()) - } + accts := util.Map(dumpForDryrunAccts, cliAddress) data, err := libgoal.MakeDryrunStateBytes(client, nil, stxns, accts, string(proto), dumpForDryrunFormat.String()) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } writeFile(outFilename, data, 0600) return @@ -1383,19 +1381,6 @@ var simulateCmd = &cobra.Command{ }, } -// unmarshalSlice converts string addresses to basics.Address -func unmarshalSlice(accts []string) ([]basics.Address, error) { - result := make([]basics.Address, 0, len(accts)) - for _, acct := range accts { - addr, err := basics.UnmarshalChecksumAddress(acct) - if err != nil { - return nil, err - } - result = append(result, addr) - } - return result, nil -} - func decodeTxnsFromFile(file string) []transactions.SignedTxn { data, err := readFile(file) if err != nil { diff --git a/cmd/goal/commands.go b/cmd/goal/commands.go index e818721153..453ae05d12 100644 --- a/cmd/goal/commands.go +++ b/cmd/goal/commands.go @@ -38,6 +38,7 @@ import ( "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" ) var log = logging.Base() @@ -524,13 +525,10 @@ func writeFile(filename string, data []byte, perm os.FileMode) error { // writeDryrunReqToFile creates dryrun request object and writes to a file func writeDryrunReqToFile(client libgoal.Client, txnOrStxn interface{}, outFilename string) (err error) { proto, _ := getProto(protoVersion) - accts, err := unmarshalSlice(dumpForDryrunAccts) - if err != nil { - reportErrorln(err.Error()) - } + accts := util.Map(dumpForDryrunAccts, cliAddress) data, err := libgoal.MakeDryrunStateBytes(client, txnOrStxn, []transactions.SignedTxn{}, accts, string(proto), dumpForDryrunFormat.String()) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } err = writeFile(outFilename, data, 0600) return diff --git a/cmd/goal/formatting_test.go b/cmd/goal/formatting_test.go index d113e3225b..81b5454c76 100644 --- a/cmd/goal/formatting_test.go +++ b/cmd/goal/formatting_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -58,54 +59,62 @@ func TestNewAppCallBytes(t *testing.T) { require.Panics(t, func() { newAppCallBytes("hello") }) } -func TestNewBoxRef(t *testing.T) { +func TestParseBoxRef(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - br := newBoxRef("str:hello") + br := parseBoxRef("str:hello") require.EqualValues(t, 0, br.appID) require.Equal(t, "str", br.name.Encoding) require.Equal(t, "hello", br.name.Value) - require.Panics(t, func() { newBoxRef("1,hello") }) - require.Panics(t, func() { newBoxRef("hello") }) + require.Panics(t, func() { parseBoxRef("1,hello") }) + require.Panics(t, func() { parseBoxRef("hello") }) - br = newBoxRef("2,str:hello") + br = parseBoxRef("2,str:hello") require.EqualValues(t, 2, br.appID) require.Equal(t, "str", br.name.Encoding) require.Equal(t, "hello", br.name.Value) } -func TestStringsToBoxRefs(t *testing.T) { +func TestParseHoldingRef(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - brs := stringsToBoxRefs([]string{"77,str:hello", "55,int:6", "int:88"}) - require.EqualValues(t, 77, brs[0].appID) - require.EqualValues(t, 55, brs[1].appID) - require.EqualValues(t, 0, brs[2].appID) + hr := parseHoldingRef("12") + require.EqualValues(t, 12, hr.assetID) + require.Zero(t, hr.address) - tbrs := translateBoxRefs(brs, []uint64{55, 77}) - require.EqualValues(t, 2, tbrs[0].Index) - require.EqualValues(t, 1, tbrs[1].Index) - require.EqualValues(t, 0, tbrs[2].Index) + hr = parseHoldingRef("1232+JUNK") + require.EqualValues(t, 1232, hr.assetID) + require.Equal(t, "JUNK", hr.address) +} + +func TestParseLocalRef(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + lr := parseLocalRef("12") + assert.EqualValues(t, 12, lr.appID) + assert.Zero(t, lr.address) - require.Panics(t, func() { translateBoxRefs(stringsToBoxRefs([]string{"addr:88"}), nil) }) - translateBoxRefs(stringsToBoxRefs([]string{"addr:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"}), nil) - // if we're here, that didn't panic/exit + lr = parseLocalRef("1232+JUNK") + assert.EqualValues(t, 1232, lr.appID) + assert.Equal(t, "JUNK", lr.address) - tbrs = translateBoxRefs(brs, []uint64{77, 55}) - require.EqualValues(t, 1, tbrs[0].Index) - require.EqualValues(t, 2, tbrs[1].Index) - require.EqualValues(t, 0, tbrs[2].Index) + lr = parseLocalRef("0+JUNK") + assert.Zero(t, lr.appID) + assert.Equal(t, "JUNK", lr.address) - require.Panics(t, func() { translateBoxRefs(brs, []uint64{55, 78}) }) - require.Panics(t, func() { translateBoxRefs(brs, []uint64{51, 77}) }) + lr = parseLocalRef("STUFF") + assert.Zero(t, lr.appID) + assert.Equal(t, "STUFF", lr.address) } func TestBytesToAppCallBytes(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + testCases := []struct { input []byte expected string diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index 68984428f8..cd6f2291f5 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -26,6 +26,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/spf13/cobra" @@ -36,6 +37,8 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/util" ) var ( @@ -454,6 +457,7 @@ func parseAppHeader() (header appInteractHeader) { if err != nil { reportErrorf("Could not open app header file %s: %v", appHdr, err) } + defer f.Close() dec := json.NewDecoder(f) err = dec.Decode(&header) @@ -477,6 +481,9 @@ var appExecuteCmd = &cobra.Command{ Short: "Execute a procedure on an application", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + reportWarnf("app interact is deprecated and will be removed soon. Please speak up if the feature matters to you.") + time.Sleep(3 * time.Second) + dataDir := datadir.EnsureSingleDataDir() client := ensureFullClient(dataDir) @@ -559,9 +566,12 @@ var appExecuteCmd = &cobra.Command{ proc.OnCompletion = "NoOp" } onCompletion := mustParseOnCompletion(proc.OnCompletion) - appAccounts := inputs.Accounts - foreignApps := inputs.ForeignApps - foreignAssets := inputs.ForeignAssets + refs := libgoal.RefBundle{ + Accounts: util.Map(inputs.Accounts, cliAddress), + Apps: util.Map(inputs.ForeignApps, func(idx uint64) basics.AppIndex { return basics.AppIndex(idx) }), + Assets: util.Map(inputs.ForeignAssets, func(idx uint64) basics.AssetIndex { return basics.AssetIndex(idx) }), + // goal app interact is old and doesn't know about holdings, locals, boxes. + } appArgs := make([][]byte, len(inputs.Args)) for i, arg := range inputs.Args { @@ -586,7 +596,7 @@ var appExecuteCmd = &cobra.Command{ localSchema = header.Query.Local.ToStateSchema() globalSchema = header.Query.Global.ToStateSchema() } - tx, err := client.MakeUnsignedApplicationCallTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, nil, onCompletion, approvalProg, clearProg, globalSchema, localSchema, 0, rejectVersion) + tx, err := client.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, onCompletion, approvalProg, clearProg, globalSchema, localSchema, 0, rejectVersion) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -632,7 +642,7 @@ var appExecuteCmd = &cobra.Command{ if !noWaitAfterSend { txn, err1 := waitForCommit(client, txid, lv) if err1 != nil { - reportErrorln(err1.Error()) + reportErrorln(err1) } if txn.ApplicationIndex != nil && *txn.ApplicationIndex != 0 { reportInfof("Created app with app index %d", *txn.ApplicationIndex) @@ -642,7 +652,7 @@ var appExecuteCmd = &cobra.Command{ // Broadcast or write transaction to file err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) if err != nil { - reportErrorln(err.Error()) + reportErrorln(err) } } }, @@ -653,6 +663,9 @@ var appQueryCmd = &cobra.Command{ Short: "Query local or global state from an application", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + reportWarnf("app interact is deprecated and will be removed soon. Please speak up if the feature matters to you.") + time.Sleep(5 * time.Second) + dataDir := datadir.EnsureSingleDataDir() client := ensureFullClient(dataDir) diff --git a/config/consensus.go b/config/consensus.go index 042b0f78ca..74bceadcfe 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -280,8 +280,9 @@ type ConsensusParams struct { // be read in the transaction MaxAppTxnForeignAssets int - // maximum number of "foreign references" (accounts, asa, app, boxes) - // that can be attached to a single app call. + // maximum number of "foreign references" (accounts, asa, app, boxes) that + // can be attached to a single app call. Modern transactions can use + // MaxAppAccess references in txn.Access to access more. MaxAppTotalTxnReferences int // maximum cost of application approval program or clear state program @@ -353,6 +354,9 @@ type ConsensusParams struct { // Number of box references allowed MaxAppBoxReferences int + // Number of references allowed in txn.Access + MaxAppAccess int + // Amount added to a txgroup's box I/O budget per box ref supplied. // For reads: the sum of the sizes of all boxes in the group must be less than I/O budget // For writes: the sum of the sizes of all boxes created or written must be less than I/O budget @@ -558,6 +562,12 @@ type ConsensusParams struct { // EnableSha512BlockHash adds an additional SHA-512 hash to the block header. EnableSha512BlockHash bool + + // EnableInnerClawbackWithoutSenderHolding allows an inner clawback (axfer + // w/ AssetSender) even if the Sender holding of the asset is not + // available. This parameters can be removed and assumed true after the + // first consensus release in which it is set true. + EnableInnerClawbackWithoutSenderHolding bool } // ProposerPayoutRules puts several related consensus parameters in one place. The same @@ -1425,6 +1435,12 @@ func initConsensusProtocols() { vFuture.EnableAppVersioning = true // if not promoted when v12 goes into effect, update logic/field.go vFuture.EnableSha512BlockHash = true + // txn.Access work + vFuture.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same + vFuture.MaxAppAccess = 16 // Twice as many, though cross products are explicit + vFuture.BytesPerBoxReference = 2048 // Count is more important that bytes, loosen up + vFuture.EnableInnerClawbackWithoutSenderHolding = true + Consensus[protocol.ConsensusFuture] = vFuture // vAlphaX versions are an separate series of consensus parameters and versions for alphanet diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index e0f7acbf69..78449fa7c2 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -60,6 +60,7 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/stateproof" + "github.com/algorand/go-algorand/util" ) // MaxTealSourceBytes sets a size limit for TEAL source programs for requests @@ -772,7 +773,7 @@ func (v2 *Handlers) GetBlockTxids(ctx echo.Context, round basics.Round) error { func NewAppCallLogs(txid string, logs []string, appIndex basics.AppIndex) model.AppCallLogs { return model.AppCallLogs{ TxId: txid, - Logs: convertSlice(logs, func(s string) []byte { return []byte(s) }), + Logs: util.Map(logs, func(s string) []byte { return []byte(s) }), ApplicationIndex: appIndex, } } diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 837b88e0a4..606a88bd73 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -40,6 +40,7 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" ) // returnError logs an internal message while returning the encoded response. @@ -77,14 +78,6 @@ func notImplemented(ctx echo.Context, internal error, external string, log loggi return returnError(ctx, http.StatusNotImplemented, internal, external, log) } -func convertSlice[X any, Y any](input []X, fn func(X) Y) []Y { - output := make([]Y, len(input)) - for i := range input { - output[i] = fn(input[i]) - } - return output -} - func convertMap[X comparable, Y, Z any](input map[X]Y, fn func(X, Y) Z) []Z { output := make([]Z, len(input)) counter := 0 @@ -96,7 +89,7 @@ func convertMap[X comparable, Y, Z any](input map[X]Y, fn func(X, Y) Z) []Z { } func stringSlice[T fmt.Stringer](s []T) []string { - return convertSlice(s, func(t T) string { return t.String() }) + return util.Map(s, func(t T) string { return t.String() }) } func sliceOrNil[T any](s []T) *[]T { @@ -441,10 +434,10 @@ func convertOpcodeTraceUnit(opcodeTraceUnit simulation.OpcodeTraceUnit) model.Si return model.SimulationOpcodeTraceUnit{ Pc: opcodeTraceUnit.PC, SpawnedInners: sliceOrNil(opcodeTraceUnit.SpawnedInners), - StackAdditions: sliceOrNil(convertSlice(opcodeTraceUnit.StackAdded, convertToAVMValue)), + StackAdditions: sliceOrNil(util.Map(opcodeTraceUnit.StackAdded, convertToAVMValue)), StackPopCount: omitEmpty(opcodeTraceUnit.StackPopCount), - ScratchChanges: sliceOrNil(convertSlice(opcodeTraceUnit.ScratchSlotChanges, convertScratchChange)), - StateChanges: sliceOrNil(convertSlice(opcodeTraceUnit.StateChanges, convertApplicationStateChange)), + ScratchChanges: sliceOrNil(util.Map(opcodeTraceUnit.ScratchSlotChanges, convertScratchChange)), + StateChanges: sliceOrNil(util.Map(opcodeTraceUnit.StateChanges, convertApplicationStateChange)), } } @@ -453,15 +446,15 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra return nil } return &model.SimulationTransactionExecTrace{ - ApprovalProgramTrace: sliceOrNil(convertSlice(txnTrace.ApprovalProgramTrace, convertOpcodeTraceUnit)), + ApprovalProgramTrace: sliceOrNil(util.Map(txnTrace.ApprovalProgramTrace, convertOpcodeTraceUnit)), ApprovalProgramHash: digestOrNil(txnTrace.ApprovalProgramHash), - ClearStateProgramTrace: sliceOrNil(convertSlice(txnTrace.ClearStateProgramTrace, convertOpcodeTraceUnit)), + ClearStateProgramTrace: sliceOrNil(util.Map(txnTrace.ClearStateProgramTrace, convertOpcodeTraceUnit)), ClearStateProgramHash: digestOrNil(txnTrace.ClearStateProgramHash), ClearStateRollback: omitEmpty(txnTrace.ClearStateRollback), ClearStateRollbackError: omitEmpty(txnTrace.ClearStateRollbackError), - LogicSigTrace: sliceOrNil(convertSlice(txnTrace.LogicSigTrace, convertOpcodeTraceUnit)), + LogicSigTrace: sliceOrNil(util.Map(txnTrace.LogicSigTrace, convertOpcodeTraceUnit)), LogicSigHash: digestOrNil(txnTrace.LogicSigHash), - InnerTrace: sliceOrNil(convertSlice(txnTrace.InnerTraces, + InnerTrace: sliceOrNil(util.Map(txnTrace.InnerTraces, func(trace simulation.TransactionTrace) model.SimulationTransactionExecTrace { return *convertTxnTrace(&trace) }), @@ -494,20 +487,20 @@ func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker) *mod Accounts: sliceOrNil(stringSlice(slices.Collect(maps.Keys(resources.Accounts)))), Assets: sliceOrNil(slices.Collect(maps.Keys(resources.Assets))), Apps: sliceOrNil(slices.Collect(maps.Keys(resources.Apps))), - Boxes: sliceOrNil(convertSlice(slices.Collect(maps.Keys(resources.Boxes)), func(box logic.BoxRef) model.BoxReference { + Boxes: sliceOrNil(util.Map(slices.Collect(maps.Keys(resources.Boxes)), func(box basics.BoxRef) model.BoxReference { return model.BoxReference{ App: box.App, Name: []byte(box.Name), } })), ExtraBoxRefs: omitEmpty(resources.NumEmptyBoxRefs), - AssetHoldings: sliceOrNil(convertSlice(slices.Collect(maps.Keys(resources.AssetHoldings)), func(holding ledgercore.AccountAsset) model.AssetHoldingReference { + AssetHoldings: sliceOrNil(util.Map(slices.Collect(maps.Keys(resources.AssetHoldings)), func(holding ledgercore.AccountAsset) model.AssetHoldingReference { return model.AssetHoldingReference{ Account: holding.Address.String(), Asset: holding.Asset, } })), - AppLocals: sliceOrNil(convertSlice(slices.Collect(maps.Keys(resources.AppLocals)), func(local ledgercore.AccountApp) model.ApplicationLocalReference { + AppLocals: sliceOrNil(util.Map(slices.Collect(maps.Keys(resources.AppLocals)), func(local ledgercore.AccountApp) model.ApplicationLocalReference { return model.ApplicationLocalReference{ Account: local.Address.String(), App: local.App, @@ -599,7 +592,7 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons return PreEncodedSimulateResponse{ Version: result.Version, LastRound: result.LastRound, - TxnGroups: convertSlice(result.TxnGroups, convertTxnGroupResult), + TxnGroups: util.Map(result.TxnGroups, convertTxnGroupResult), EvalOverrides: evalOverrides, ExecTraceConfig: result.TraceConfig, InitialStates: convertSimulateInitialStates(result.InitialStates), diff --git a/data/basics/address.go b/data/basics/address.go index 8d6e187ffa..c34a800860 100644 --- a/data/basics/address.go +++ b/data/basics/address.go @@ -55,7 +55,7 @@ func UnmarshalChecksumAddress(address string) (Address, error) { decoded, err := base32Encoder.DecodeString(address) if err != nil { - return Address{}, fmt.Errorf("failed to decode address %s to base 32", address) + return Address{}, fmt.Errorf("failed to decode address %s from base 32", address) } var short Address if len(decoded) < len(short) { diff --git a/data/basics/testing/nearzero.go b/data/basics/testing/nearzero.go new file mode 100644 index 0000000000..4620e77c72 --- /dev/null +++ b/data/basics/testing/nearzero.go @@ -0,0 +1,187 @@ +// Copyright (C) 2019-2025 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package testing + +import ( + "reflect" + "testing" + "time" +) + +// NearZeros takes a sample in order to retrieve its type. It returns a slice of +// the same type in which each element of the slice is the same type as the +// sample, but exactly one field (or sub-field) is set to a non-zero value. It +// returns one example for every sub-field. +func NearZeros(t *testing.T, sample any) []any { + typ := reflect.TypeOf(sample) + // If sample is a pointer, work with the underlying type. + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + if typ.Kind() != reflect.Struct { + t.Fatalf("NearZeros: sample must be a struct, got %s", typ.Kind()) + } + paths := CollectPaths(typ, []int{}) + var results []any + for _, path := range paths { + inst := makeInstanceWithNonZeroField(typ, path) + results = append(results, inst) + } + return results +} + +// CollectPaths walks over the struct type (recursively) and returns a slice of +// index paths. Each path points to exactly one (exported) sub-field. +func CollectPaths(typ reflect.Type, prefix []int) [][]int { + var paths [][]int + + switch typ.Kind() { + case reflect.Ptr, reflect.Slice, reflect.Array: + // Look through container to the element + return CollectPaths(typ.Elem(), prefix) + + case reflect.Map: + // Record as a leaf because we will just make a single entry in the map + paths = append(paths, prefix) + + case reflect.Struct: + // Special case: skip known value-type structs like time.Time + if typ == reflect.TypeOf(time.Time{}) { + return [][]int{prefix} + } + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if !field.IsExported() { + continue + } + newPath := append(append([]int(nil), prefix...), i) + subPaths := CollectPaths(field.Type, newPath) + + // If recursion yielded deeper paths, use them + if len(subPaths) > 0 { + paths = append(paths, subPaths...) + } else { + // Otherwise, it's a leaf field — include it + paths = append(paths, newPath) + } + } + + default: + // Primitive type — record this as a leaf + paths = append(paths, prefix) + } + + return paths +} + +// makeInstanceWithNonZeroField creates a new instance of type typ and sets exactly one +// field (identified by the fieldPath) to a non-zero value. +func makeInstanceWithNonZeroField(typ reflect.Type, fieldPath []int) any { + // Create a new instance (as a value, not pointer). + inst := reflect.New(typ).Elem() + setFieldToNonZero(inst, fieldPath) + return inst.Interface() +} + +// setFieldToNonZero navigates along the given path in the value v and sets that +// field to a non-zero value. The path is a slice of field indices. +func setFieldToNonZero(v reflect.Value, path []int) { + // Walk down the struct fields until the final field. + for i := 0; i < len(path)-1; i++ { + v = v.Field(path[i]) + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + case reflect.Slice: + if v.Len() == 0 { + slice := reflect.MakeSlice(v.Type(), 1, 1) + v.Set(slice) + } + v = v.Index(0) + case reflect.Array: + v = v.Index(0) // Already allocated, just descend + } + } + // Set the final field to an appropriate non-zero value. + field := v.Field(path[len(path)-1]) + if field.CanSet() { + field.Set(exampleValue(field.Type())) + } +} + +// exampleValue returns a non-zero value for a given type. +// For composite types (like arrays), it sets one element. +func exampleValue(t reflect.Type) reflect.Value { + switch t.Kind() { + case reflect.String: + return reflect.ValueOf("non-zero").Convert(t) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return reflect.ValueOf(1).Convert(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return reflect.ValueOf(1).Convert(t) + case reflect.Bool: + return reflect.ValueOf(true).Convert(t) + case reflect.Float32, reflect.Float64: + return reflect.ValueOf(1.23).Convert(t) + case reflect.Ptr: + // For pointers, allocate a new element and set it to non-zero. + elem := reflect.New(t.Elem()) + elem.Elem().Set(exampleValue(t.Elem())) + return elem + case reflect.Slice: + // Create a slice with one element. + slice := reflect.MakeSlice(t, 1, 1) + slice.Index(0).Set(exampleValue(t.Elem())) + return slice + case reflect.Map: + // Create a map with one key-value pair. + m := reflect.MakeMap(t) + // We put in an _empty_ value, because we want to ensure that a map with + // a value is considered non-zero. The fact that the value is zero is + // irrelevant. + e := reflect.New(t.Elem()).Elem() + m.SetMapIndex(exampleValue(t.Key()), e) + return m + case reflect.Array: + // Create an array and set the first element. + arr := reflect.New(t).Elem() + if t.Len() > 0 { + arr.Index(0).Set(exampleValue(t.Elem())) + } + return arr + case reflect.Struct: + // For structs, set the first exported field (if any). + s := reflect.New(t).Elem() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.IsExported() { + fv := s.Field(i) + if fv.CanSet() { + fv.Set(exampleValue(f.Type)) + break + } + } + } + return s + default: + panic("unable to make a non-zero: " + t.String()) + } +} diff --git a/data/basics/testing/nearzero_test.go b/data/basics/testing/nearzero_test.go new file mode 100644 index 0000000000..eb22932b5f --- /dev/null +++ b/data/basics/testing/nearzero_test.go @@ -0,0 +1,140 @@ +// Copyright (C) 2019-2025 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package testing + +import ( + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" +) + +func TestNearZeros(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := assert.New(t) + a.Len(NearZeros(t, struct{}{}), 0) + + type one struct { + B bool + } + a.Len(NearZeros(t, one{}), 1) + + type two struct { + I int + J int + } + a.Len(NearZeros(t, two{}), 2) + + // struct three has structs inside + type three struct { + One one + Two two + } + a.Len(NearZeros(t, three{}), 3) + + // struct four has a pointer and an array + type four struct { + One one + Two *two + Array [3]int + } + a.Len(NearZeros(t, four{}), 4) + + // Show that Two is allocated (twice, once for I, once for J) + count := 0 + for _, nz := range NearZeros(t, four{}) { + f := nz.(four) + if f.Two != nil { + count++ + } + } + a.Equal(2, count) + + // struct five has a slice + type five struct { + Two1 two + Two2 two + Slice []int + } + a.Len(NearZeros(t, five{}), 5) + + // Show that Slice is allocated once + count = 0 + for _, nz := range NearZeros(t, five{}) { + f := nz.(five) + if f.Slice != nil { + count++ + } + } + a.Equal(1, count) + + // struct size has a slice of struct + type six struct { + Slice1 []one + Slice2 []two + Slice3 []three + } + a.Len(NearZeros(t, six{}), 6) + + // Show that Slice2 is allocated twice, in order to fill Slice2[0].{I,J} + count = 0 + for _, nz := range NearZeros(t, six{}) { + f := nz.(six) + if f.Slice2 != nil { + count++ + a.True(f.Slice2[0].I > 0 || f.Slice2[0].J > 0) + } + } + a.Equal(2, count) +} + +func TestUnexported(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + type unexported struct { + a int + B int + } + assert.Len(t, NearZeros(t, unexported{}), 1) +} + +func TestMap(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + type mapint struct { + A map[int]int + } + assert.Len(t, NearZeros(t, mapint{}), 1) + assert.Zero(t, NearZeros(t, mapint{})[0].(mapint).A[1]) + + type mapstring struct { + A map[int]string + } + assert.Len(t, NearZeros(t, mapstring{}), 1) + assert.Zero(t, NearZeros(t, mapstring{})[0].(mapstring).A[1]) + + type mapstruct2 struct { + A map[int]struct{ A, B, C int } + } + assert.Len(t, NearZeros(t, mapstruct2{}), 1) + assert.Len(t, NearZeros(t, mapstruct2{})[0].(mapstruct2).A, 1) + assert.Zero(t, NearZeros(t, mapstruct2{})[0].(mapstruct2).A[1]) +} diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 84c762a7ac..431f9abe84 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -310,6 +310,27 @@ type AssetIndex uint64 // AppParams type AppIndex uint64 +// BoxRef is the "hydrated" form of a transactions.BoxRef - it has the actual +// app id, not an index +type BoxRef struct { + App AppIndex + Name string +} + +// HoldingRef is the "hydrated" form of a transactions.HoldingRef - it has the +// actual asset id and address, not indices +type HoldingRef struct { + Asset AssetIndex + Address Address +} + +// LocalRef is the "hydrated" form of a transactions.LocalRef - it has the +// actual app id and address, not indices +type LocalRef struct { + App AppIndex + Address Address +} + // CreatableIndex represents either an AssetIndex or AppIndex, which come from // the same namespace of indices as each other (both assets and apps are // "creatables") diff --git a/data/transactions/application.go b/data/transactions/application.go index 3e200d9f1f..56069bf6be 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -17,6 +17,7 @@ package transactions import ( + "errors" "fmt" "slices" @@ -54,6 +55,12 @@ const ( // can contain. Its value is verified against consensus parameters in // TestEncodedAppTxnAllocationBounds encodedMaxBoxes = 32 + + // encodedMaxAccess sets the allocation bound for the maximum number of + // references in Access that a transaction decoded off of the wire can + // contain. Its value is verified against consensus parameters in + // TestEncodedAppTxnAllocationBounds + encodedMaxAccess = 64 ) // OnCompletion is an enum representing some layer 1 side effect that an @@ -119,22 +126,29 @@ type ApplicationCallTxnFields struct { // the app or asset id). Accounts []basics.Address `codec:"apat,allocbound=encodedMaxAccounts"` + // ForeignAssets are asset IDs for assets whose AssetParams + // (and since v4, Holdings) may be read by the executing + // ApprovalProgram or ClearStateProgram. + ForeignAssets []basics.AssetIndex `codec:"apas,allocbound=encodedMaxForeignAssets"` + // ForeignApps are application IDs for applications besides // this one whose GlobalState (or Local, since v4) may be read // by the executing ApprovalProgram or ClearStateProgram. ForeignApps []basics.AppIndex `codec:"apfa,allocbound=encodedMaxForeignApps"` + // Access unifies `Accounts`, `ForeignApps`, `ForeignAssets`, and `Boxes` + // under a single list. It removes all implicitly available resources, so + // "cross-product" resources (holdings and locals) must be explicitly + // listed, as well as app accounts, even the app account of the called app! + // Transactions using Access may not use the other lists. + Access []ResourceRef `codec:"al,allocbound=encodedMaxAccess"` + // Boxes are the boxes that can be accessed by this transaction (and others // in the same group). The Index in the BoxRef is the slot of ForeignApps - // that the name is associated with (shifted by 1, so 0 indicates "current + // that the name is associated with (shifted by 1. 0 indicates "current // app") Boxes []BoxRef `codec:"apbx,allocbound=encodedMaxBoxes"` - // ForeignAssets are asset IDs for assets whose AssetParams - // (and since v4, Holdings) may be read by the executing - // ApprovalProgram or ClearStateProgram. - ForeignAssets []basics.AssetIndex `codec:"apas,allocbound=encodedMaxForeignAssets"` - // LocalStateSchema specifies the maximum number of each type that may // appear in the local key/value store of users who opt in to this // application. This field is only used during application creation @@ -174,12 +188,181 @@ type ApplicationCallTxnFields struct { // method below! } -// BoxRef names a box by the slot +// ResourceRef names a single resource +type ResourceRef struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // Only one of these may be set + Address basics.Address `codec:"d"` + Asset basics.AssetIndex `codec:"s"` + App basics.AppIndex `codec:"p"` + Holding HoldingRef `codec:"h"` + Locals LocalsRef `codec:"l"` + Box BoxRef `codec:"b"` +} + +// Empty ResourceRefs are allowed, as they ask for a box quota bump. +func (rr ResourceRef) Empty() bool { + return rr.Address.IsZero() && rr.Asset == 0 && rr.App == 0 && + rr.Holding.Empty() && rr.Locals.Empty() && rr.Box.Empty() +} + +// wellFormed checks that a ResourceRef is a proper member of `access. `rr` is +// either empty a single kind of resource. Any internal indices point to proper +// locations inside `access`. +func (rr ResourceRef) wellFormed(access []ResourceRef, proto config.ConsensusParams) error { + // Count the number of non-empty fields + count := 0 + // The "basic" resources are inherently wellFormed + if !rr.Address.IsZero() { + count++ + } + if rr.Asset != 0 { + count++ + } + if rr.App != 0 { + count++ + } + // The references that have indices need to be checked + if !rr.Holding.Empty() { + if _, _, err := rr.Holding.Resolve(access, basics.Address{}); err != nil { + return err + } + count++ + } + if !rr.Locals.Empty() { + if _, _, err := rr.Locals.Resolve(access, basics.Address{}); err != nil { + return err + } + count++ + } + if !rr.Box.Empty() { + if proto.EnableBoxRefNameError && len(rr.Box.Name) > proto.MaxAppKeyLen { + return fmt.Errorf("tx.Access box Name too long, max len %d bytes", proto.MaxAppKeyLen) + } + if _, _, err := rr.Box.Resolve(access); err != nil { + return err + } + count++ + } + switch count { + case 0: + if !rr.Empty() { // If it's not one of those, it has to be empty + return fmt.Errorf("tx.Access with unknown content") + } + return nil + case 1: + return nil + default: + return fmt.Errorf("tx.Access element has fields from multiple types") + } +} + +// HoldingRef names a holding by referring to an Address and Asset that appear +// earlier in the Access list (0 is special cased) +type HoldingRef struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Address uint64 `codec:"d"` // 0=Sender,n-1=index into the Access list, which must be an Address + Asset uint64 `codec:"s"` // n-1=index into the Access list, which must be an Asset +} + +// Empty does the obvious. An empty HoldingRef has no meaning, since we define +// hr.Asset to be a 1-based index for consistency with LocalRef (which does it +// because 0 means "this app") +func (hr HoldingRef) Empty() bool { + return hr == HoldingRef{} +} + +// Resolve looks up the referenced address and asset in the access list +func (hr HoldingRef) Resolve(access []ResourceRef, sender basics.Address) (basics.Address, basics.AssetIndex, error) { + address := sender // Returned when hr.Address == 0 + if hr.Address != 0 { + if hr.Address > uint64(len(access)) { // recall that Access is 1-based + return basics.Address{}, 0, fmt.Errorf("holding Address reference %d outside tx.Access", hr.Address) + } + address = access[hr.Address-1].Address + if address.IsZero() { + return basics.Address{}, 0, fmt.Errorf("holding Address reference %d is not an Address", hr.Address) + } + } + if hr.Asset == 0 || hr.Asset > uint64(len(access)) { // 1-based + return basics.Address{}, 0, fmt.Errorf("holding Asset reference %d outside tx.Access", hr.Asset) + } + asset := access[hr.Asset-1].Asset + if asset == 0 { + return basics.Address{}, 0, fmt.Errorf("holding Asset reference %d is not an Asset", hr.Asset) + } + return address, asset, nil +} + +// LocalsRef names a local state by referring to an Address and App that appear +// earlier in the Access list (0 is special cased) +type LocalsRef struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Address uint64 `codec:"d"` // 0=Sender,n-1=index into the Access list, which must be an Address + App uint64 `codec:"p"` // 0=ApplicationID,n-1=index into the Access list, which must be an App +} + +// Empty does the obvious. An empty LocalsRef makes no sense, because it would +// mean "give access to the sender's locals for this app", which is implicit. +func (lr LocalsRef) Empty() bool { + return lr == LocalsRef{} +} + +// Resolve looks up the referenced address and app in the access list. 0 is +// returned if the App index is 0, meaning "current app". +func (lr LocalsRef) Resolve(access []ResourceRef, sender basics.Address) (basics.Address, basics.AppIndex, error) { + address := sender // Returned when lr.Address == 0 + if lr.Address != 0 { + if lr.Address > uint64(len(access)) { // recall that Access is 1-based + return basics.Address{}, 0, fmt.Errorf("locals Address reference %d outside tx.Access", lr.Address) + } + address = access[lr.Address-1].Address + if address.IsZero() { + return basics.Address{}, 0, fmt.Errorf("locals Address reference %d is not an Address", lr.Address) + } + } + if lr.App == 0 || lr.App > uint64(len(access)) { // 1-based + return basics.Address{}, 0, fmt.Errorf("locals App reference %d outside tx.Access", lr.App) + } + app := access[lr.App-1].App + if app == 0 { + return basics.Address{}, 0, fmt.Errorf("locals App reference %d is not an App", lr.App) + } + return address, app, nil +} + +// BoxRef names a box by the slot. In the Boxes field, `i` is an index into +// ForeignApps. As an entry in Access, `i` is a index into Access itself. type BoxRef struct { _struct struct{} `codec:",omitempty,omitemptyarray"` + Index uint64 `codec:"i"` + Name []byte `codec:"n,allocbound=bounds.MaxBytesKeyValueLen"` +} + +// Empty does the obvious. But the meaning is not obvious. An empty BoxRef just +// adds to the read/write quota of the transaction. In tx.Access, _any_ empty +// ResourceRef bumps the read/write quota. (We cannot distinguish the type when +// all are empty.) +func (br BoxRef) Empty() bool { + return br.Index == 0 && br.Name == nil +} - Index uint64 `codec:"i"` - Name []byte `codec:"n,allocbound=bounds.MaxBytesKeyValueLen"` +// Resolve looks up the referenced app and returns it with the name. 0 is +// returned if the App index is 0, meaning "current app". +func (br BoxRef) Resolve(access []ResourceRef) (basics.AppIndex, string, error) { + switch { + case br.Index == 0: + return 0, string(br.Name), nil + case br.Index <= uint64(len(access)): // 1-based + rr := access[br.Index-1] + if app := rr.App; app != 0 { + return app, string(br.Name), nil + } + return 0, "", fmt.Errorf("box Index reference %d is not an App", br.Index) + default: + return 0, "", fmt.Errorf("box Index %d outside tx.Access", br.Index) + } } // Empty indicates whether or not all the fields in the @@ -206,6 +389,9 @@ func (ac *ApplicationCallTxnFields) Empty() bool { if ac.Boxes != nil { return false } + if ac.Access != nil { + return false + } if ac.LocalStateSchema != (basics.StateSchema{}) { return false } @@ -235,7 +421,7 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro case NoOpOC, OptInOC, CloseOutOC, ClearStateOC, UpdateApplicationOC, DeleteApplicationOC: /* ok */ default: - return fmt.Errorf("invalid application OnCompletion") + return fmt.Errorf("invalid application OnCompletion (%d)", ac.OnCompletion) } if !proto.EnableAppVersioning && ac.RejectVersion > 0 { @@ -263,9 +449,11 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro effectiveEPP := ac.ExtraProgramPages // Schemas and ExtraProgramPages may only be set during application creation if ac.ApplicationID != 0 { - if ac.LocalStateSchema != (basics.StateSchema{}) || - ac.GlobalStateSchema != (basics.StateSchema{}) { - return fmt.Errorf("local and global state schemas are immutable") + if ac.GlobalStateSchema != (basics.StateSchema{}) { + return fmt.Errorf("tx.GlobalStateSchema is immutable") + } + if ac.LocalStateSchema != (basics.StateSchema{}) { + return fmt.Errorf("tx.LocalStateSchema is immutable") } if ac.ExtraProgramPages != 0 { return fmt.Errorf("tx.ExtraProgramPages is immutable") @@ -276,7 +464,8 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro // Limit total number of arguments if len(ac.ApplicationArgs) > proto.MaxAppArgs { - return fmt.Errorf("too many application args, max %d", proto.MaxAppArgs) + return fmt.Errorf("tx.ApplicationArgs has too many arguments. %d > %d", + len(ac.ApplicationArgs), proto.MaxAppArgs) } // Sum up argument lengths @@ -287,30 +476,51 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro // Limit total length of all arguments if argSum > uint64(proto.MaxAppTotalArgLen) { - return fmt.Errorf("application args total length too long, max len %d bytes", proto.MaxAppTotalArgLen) - } - - // Limit number of accounts referred to in a single ApplicationCall - if len(ac.Accounts) > proto.MaxAppTxnAccounts { - return fmt.Errorf("tx.Accounts too long, max number of accounts is %d", proto.MaxAppTxnAccounts) + return fmt.Errorf("tx.ApplicationArgs total length is too long. %d > %d", + argSum, proto.MaxAppTotalArgLen) } - // Limit number of other app global states referred to - if len(ac.ForeignApps) > proto.MaxAppTxnForeignApps { - return fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is %d", proto.MaxAppTxnForeignApps) - } - - if len(ac.ForeignAssets) > proto.MaxAppTxnForeignAssets { - return fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is %d", proto.MaxAppTxnForeignAssets) - } + if len(ac.Access) > 0 { + if len(ac.Access) > proto.MaxAppAccess { + return fmt.Errorf("tx.Access too long, max number of references is %d", proto.MaxAppAccess) + } + // When ac.Access is used, no other references are allowed + if len(ac.Accounts) > 0 { + return errors.New("tx.Accounts can't be used when tx.Access is used") + } + if len(ac.ForeignApps) > 0 { + return errors.New("tx.ForeignApps can't be used when tx.Access is used") + } + if len(ac.ForeignAssets) > 0 { + return errors.New("tx.ForeignAssets can't be used when tx.Access is used") + } + if len(ac.Boxes) > 0 { + return errors.New("tx.Boxes can't be used when tx.Access is used") + } - if len(ac.Boxes) > proto.MaxAppBoxReferences { - return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences) - } + for _, rr := range ac.Access { + if err := rr.wellFormed(ac.Access, proto); err != nil { + return err + } + } + } else { + if len(ac.Accounts) > proto.MaxAppTxnAccounts { + return fmt.Errorf("tx.Accounts too long, max number of accounts is %d", proto.MaxAppTxnAccounts) + } + if len(ac.ForeignApps) > proto.MaxAppTxnForeignApps { + return fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is %d", proto.MaxAppTxnForeignApps) + } + if len(ac.ForeignAssets) > proto.MaxAppTxnForeignAssets { + return fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is %d", proto.MaxAppTxnForeignAssets) + } + if len(ac.Boxes) > proto.MaxAppBoxReferences { + return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences) + } - // Limit the sum of all types of references that bring in account records - if len(ac.Accounts)+len(ac.ForeignApps)+len(ac.ForeignAssets)+len(ac.Boxes) > proto.MaxAppTotalTxnReferences { - return fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = %d", proto.MaxAppTotalTxnReferences) + // Limit the sum of all types of references that bring in resource records + if len(ac.Accounts)+len(ac.ForeignApps)+len(ac.ForeignAssets)+len(ac.Boxes) > proto.MaxAppTotalTxnReferences { + return fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = %d", proto.MaxAppTotalTxnReferences) + } } if ac.ExtraProgramPages > uint32(proto.MaxExtraAppProgramPages) { @@ -332,11 +542,13 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro } if ac.LocalStateSchema.NumEntries() > proto.MaxLocalSchemaEntries { - return fmt.Errorf("tx.LocalStateSchema too large, max number of keys is %d", proto.MaxLocalSchemaEntries) + return fmt.Errorf("tx.LocalStateSchema is too large. %d > %d", + ac.LocalStateSchema.NumEntries(), proto.MaxLocalSchemaEntries) } if ac.GlobalStateSchema.NumEntries() > proto.MaxGlobalSchemaEntries { - return fmt.Errorf("tx.GlobalStateSchema too large, max number of keys is %d", proto.MaxGlobalSchemaEntries) + return fmt.Errorf("tx.GlobalStateSchema is too large. %d > %d", + ac.GlobalStateSchema.NumEntries(), proto.MaxGlobalSchemaEntries) } return nil @@ -362,7 +574,7 @@ func (ac ApplicationCallTxnFields) WellSizedPrograms(extraPages uint32, proto co // AddressByIndex converts an integer index into an address associated with the // transaction. Index 0 corresponds to the transaction sender, and an index > 0 -// corresponds to an offset into txn.Accounts. Returns an error if the index is +// corresponds to an offset into txn.Accounts or txn.Access. Returns an error if the index is // not valid. func (ac *ApplicationCallTxnFields) AddressByIndex(accountIdx uint64, sender basics.Address) (basics.Address, error) { // Index 0 always corresponds to the sender @@ -370,10 +582,24 @@ func (ac *ApplicationCallTxnFields) AddressByIndex(accountIdx uint64, sender bas return sender, nil } + if ac.Access != nil { + // An index > 0 corresponds to an offset into txn.Access. Check to + // make sure the index is valid. + if accountIdx > uint64(len(ac.Access)) { + return basics.Address{}, fmt.Errorf("invalid Account reference %d exceeds length of tx.Access %d", accountIdx, len(ac.Access)) + } + // And now check that the index refers to an Address + rr := ac.Access[accountIdx-1] + if rr.Address.IsZero() { + return basics.Address{}, fmt.Errorf("address reference %d is not an Address in tx.Access", accountIdx) + } + return rr.Address, nil + } + // An index > 0 corresponds to an offset into txn.Accounts. Check to // make sure the index is valid. if accountIdx > uint64(len(ac.Accounts)) { - return basics.Address{}, fmt.Errorf("invalid Account reference %d", accountIdx) + return basics.Address{}, fmt.Errorf("invalid Account reference %d exceeds length of tx.Accounts %d", accountIdx, len(ac.Accounts)) } // accountIdx must be in [1, len(ac.Accounts)] @@ -381,18 +607,23 @@ func (ac *ApplicationCallTxnFields) AddressByIndex(accountIdx uint64, sender bas } // IndexByAddress converts an address into an integer offset into [txn.Sender, -// txn.Accounts[0], ...], returning the index at the first match. It returns -// an error if there is no such match. +// tx.[0], ...], returning the index at the first match. is +// tx.Access or tx.Accounts. It returns an error if there is no such match. func (ac *ApplicationCallTxnFields) IndexByAddress(target basics.Address, sender basics.Address) (uint64, error) { // Index 0 always corresponds to the sender if target == sender { return 0, nil } + // Try ac.Access first. Remember only one of Access or Accounts can be set. + if idx := slices.IndexFunc(ac.Access, func(rr ResourceRef) bool { return rr.Address == target }); idx != -1 { + return uint64(idx) + 1, nil + } + // Otherwise we index into ac.Accounts if idx := slices.Index(ac.Accounts, target); idx != -1 { return uint64(idx) + 1, nil } - return 0, fmt.Errorf("invalid Account reference %s", target) + return 0, fmt.Errorf("invalid Account reference %s does not appear in resource array", target) } diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index ae36fac2f8..aac9855967 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -18,7 +18,7 @@ package transactions import ( "fmt" - "reflect" + "slices" "strings" "testing" @@ -27,90 +27,40 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) -func TestApplicationCallFieldsNotChanged(t *testing.T) { +func TestResourceRefEmpty(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - af := ApplicationCallTxnFields{} - s := reflect.ValueOf(&af).Elem() - - if s.NumField() != 14 { - t.Errorf("You added or removed a field from transactions.ApplicationCallTxnFields. " + - "Please ensure you have updated the Empty() method and then " + - "fix this test") + assert.True(t, ResourceRef{}.Empty()) + for _, nz := range basics_testing.NearZeros(t, ResourceRef{}) { + rr := nz.(ResourceRef) + assert.False(t, rr.Empty(), "Empty is disregarding a non-zero field in %+v", rr) } } func TestApplicationCallFieldsEmpty(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - a := require.New(t) + a := assert.New(t) ac := ApplicationCallTxnFields{} a.True(ac.Empty()) - ac.ApplicationID = 1 - a.False(ac.Empty()) - - ac.ApplicationID = 0 - ac.OnCompletion = 1 - a.False(ac.Empty()) - - ac.OnCompletion = 0 - ac.ApplicationArgs = make([][]byte, 1) - a.False(ac.Empty()) - - ac.ApplicationArgs = nil - ac.RejectVersion = 1 - a.False(ac.Empty()) - - ac.RejectVersion = 0 - ac.Accounts = make([]basics.Address, 1) - a.False(ac.Empty()) - - ac.Accounts = nil - ac.ForeignApps = make([]basics.AppIndex, 1) - a.False(ac.Empty()) - - ac.ForeignApps = nil - ac.ForeignAssets = make([]basics.AssetIndex, 1) - a.False(ac.Empty()) - - ac.ForeignAssets = nil - ac.LocalStateSchema = basics.StateSchema{NumUint: 1} - a.False(ac.Empty()) - - ac.LocalStateSchema = basics.StateSchema{} - ac.Boxes = make([]BoxRef, 1) - a.False(ac.Empty()) - - ac.Boxes = nil - ac.GlobalStateSchema = basics.StateSchema{NumUint: 1} - a.False(ac.Empty()) - - ac.GlobalStateSchema = basics.StateSchema{} - ac.ApprovalProgram = []byte{1} - a.False(ac.Empty()) - - ac.ApprovalProgram = []byte{} - a.False(ac.Empty()) - - ac.ApprovalProgram = nil - ac.ClearStateProgram = []byte{1} - a.False(ac.Empty()) - - ac.ClearStateProgram = []byte{} - a.False(ac.Empty()) - - ac.ClearStateProgram = nil - a.True(ac.Empty()) + for _, nz := range basics_testing.NearZeros(t, ac) { + fields := nz.(ApplicationCallTxnFields) + a.False(fields.Empty(), "Empty is disregarding a non-zero field in %+v", fields) + } } func TestEncodedAppTxnAllocationBounds(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // ensure that all the supported protocols have value limits less or // equal to their corresponding codec allocbounds @@ -130,6 +80,220 @@ func TestEncodedAppTxnAllocationBounds(t *testing.T) { if proto.MaxAppBoxReferences > encodedMaxBoxes { require.Failf(t, "proto.MaxAppBoxReferences > encodedMaxBoxes", "protocol version = %s", protoVer) } + if proto.MaxAppAccess > encodedMaxAccess { + require.Failf(t, "proto.MaxAppAccess > encodedMaxAccess", "protocol version = %s", protoVer) + } + } +} + +func TestAppCallAccessWellFormed(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + preAccessCV := protocol.ConsensusV40 + addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA") + require.NoError(t, err) + + cases := []struct { + expectedError string + cv protocol.ConsensusVersion // defaults to future if not set + ac ApplicationCallTxnFields + }{ + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: slices.Repeat([]ResourceRef{{}}, 16), + }, + }, + { + expectedError: "tx.Access too long, max number of references is 0", + cv: preAccessCV, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{}}, + }, + }, + { + expectedError: "tx.Access too long, max number of references is 16", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: slices.Repeat([]ResourceRef{{}}, 17), + }, + }, + { + expectedError: "tx.Accounts can't be used when tx.Access is used", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{}}, + Accounts: []basics.Address{addr1}, + }, + }, + { + expectedError: "tx.ForeignAssets can't be used when tx.Access is used", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{}}, + ForeignAssets: []basics.AssetIndex{2}, + }, + }, + { + expectedError: "tx.ForeignApps can't be used when tx.Access is used", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{}}, + ForeignApps: []basics.AppIndex{3}, + }, + }, + { + expectedError: "tx.Boxes can't be used when tx.Access is used", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{}}, + Boxes: []BoxRef{{Index: 0}}, + }, + }, + + // Exercise holdings + { + expectedError: "holding Asset reference 2 outside tx.Access", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Holding: HoldingRef{Asset: 2}}}, + }, + }, + { + expectedError: "holding Asset reference 1 is not an Asset", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Holding: HoldingRef{Asset: 1}}}, + }, + }, + { + expectedError: "holding Asset reference 0 outside tx.Access", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{ + {Address: basics.Address{0xaa}}, + {Holding: HoldingRef{Address: 1}}, + }, + }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{ + {Address: basics.Address{0xaa}}, + {Asset: 99}, + {Holding: HoldingRef{Address: 1, Asset: 2}}, + }, + }, + }, + + // Exercise locals + { + expectedError: "locals App reference 2 outside tx.Access", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Locals: LocalsRef{App: 2}}}, + }, + }, + { + expectedError: "locals App reference 1 is not an App", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Locals: LocalsRef{App: 1}}}, + }, + }, + { + expectedError: "locals App reference 0 outside tx.Access", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{ + {Address: basics.Address{0xaa}}, + {Locals: LocalsRef{Address: 1}}, + }, + }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{ + {Address: basics.Address{0xaa}}, + {App: 99}, + {Locals: LocalsRef{Address: 1, App: 2}}, + }, + }, + }, + + // Exercise boxes + { + expectedError: "box Index 2 outside tx.Access", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Box: BoxRef{Index: 2}}}, + }, + }, + { + expectedError: "box Index reference 1 is not an App", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Box: BoxRef{Index: 1}}}, + }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{App: 20}, {Box: BoxRef{Index: 1}}}, + }, + }, + { + expectedError: "tx.Access box Name too long, max len 64 bytes", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Box: BoxRef{Name: make([]byte, 65)}}}, + }, + }, + + // Multiple uses in ResourceRef + { + expectedError: "tx.Access element has fields from multiple types", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Address: basics.Address{0x01}, Box: BoxRef{Name: []byte("a")}}}, + }, + }, + { + expectedError: "tx.Access element has fields from multiple types", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{App: 10, Locals: LocalsRef{App: 1}}}, + }, + }, + { + expectedError: "tx.Access element has fields from multiple types", + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{{Asset: 10, Holding: HoldingRef{Asset: 1}}}, + }, + }, + } + for i, tc := range cases { + name := fmt.Sprintf("i=%d", i) + if tc.expectedError != "" { + name = tc.expectedError + } + t.Run(name, func(t *testing.T) { + cv := tc.cv + if cv == "" { + cv = protocol.ConsensusFuture + } + err := tc.ac.wellFormed(config.Consensus[cv]) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + } + }) } } @@ -210,126 +374,86 @@ func TestAppCallVersioningWellFormed(t *testing.T) { func TestAppCallCreateWellFormed(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - curProto := config.Consensus[protocol.ConsensusCurrentVersion] - futureProto := config.Consensus[protocol.ConsensusFuture] - addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA") - require.NoError(t, err) v5 := []byte{0x05} v6 := []byte{0x06} - - usecases := []struct { - tx Transaction - proto config.ConsensusParams + cases := []struct { expectedError string + cv protocol.ConsensusVersion // defaults to future if not set + ac ApplicationCallTxnFields }{ { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: Header{ - Sender: addr1, - Fee: basics.MicroAlgos{Raw: 1000}, - LastValid: 105, - FirstValid: 100, - }, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, + ac: ApplicationCallTxnFields{ + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, }, - proto: curProto, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: Header{ - Sender: addr1, - Fee: basics.MicroAlgos{Raw: 1000}, - LastValid: 105, - FirstValid: 100, - }, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 0, + ac: ApplicationCallTxnFields{ + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + ExtraProgramPages: 0, }, - proto: curProto, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: Header{ - Sender: addr1, - Fee: basics.MicroAlgos{Raw: 1000}, - LastValid: 105, - FirstValid: 100, - }, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 3, + ac: ApplicationCallTxnFields{ + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + ExtraProgramPages: 3, }, - proto: futureProto, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: Header{ - Sender: addr1, - Fee: basics.MicroAlgos{Raw: 1000}, - LastValid: 105, - FirstValid: 100, - }, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 0, + ac: ApplicationCallTxnFields{ + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, }, - proto: futureProto, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: Header{ - Sender: addr1, - Fee: basics.MicroAlgos{Raw: 1000}, - LastValid: 105, - FirstValid: 100, - }, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApprovalProgram: v5, - ClearStateProgram: v6, - }, + expectedError: "program version mismatch", + ac: ApplicationCallTxnFields{ + ApprovalProgram: v5, + ClearStateProgram: v6, }, - proto: futureProto, - expectedError: "mismatch", }, } - for i, usecase := range usecases { - t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { - err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto) - if usecase.expectedError != "" { - require.Error(t, err) - require.Contains(t, err.Error(), usecase.expectedError) + for i, tc := range cases { + name := fmt.Sprintf("i=%d", i) + if tc.expectedError != "" { + name = tc.expectedError + } + t.Run(name, func(t *testing.T) { + cv := tc.cv + if cv == "" { + cv = protocol.ConsensusFuture + } + err := tc.ac.wellFormed(config.Consensus[cv]) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + } + // test the same thing for update, unless test has epp, which is illegal in update + if tc.ac.ExtraProgramPages != 0 { + return + } + tc.ac.OnCompletion = UpdateApplicationOC + tc.ac.ApplicationID = 1 + err = tc.ac.wellFormed(config.Consensus[cv]) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) } else { require.NoError(t, err) } @@ -339,284 +463,329 @@ func TestAppCallCreateWellFormed(t *testing.T) { func TestWellFormedErrors(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() + + cv27 := protocol.ConsensusV27 + cv28 := protocol.ConsensusV28 + cv32 := protocol.ConsensusV32 + cv36 := protocol.ConsensusV36 - futureProto := config.Consensus[protocol.ConsensusFuture] - protoV27 := config.Consensus[protocol.ConsensusV27] - protoV28 := config.Consensus[protocol.ConsensusV28] - protoV32 := config.Consensus[protocol.ConsensusV32] - protoV36 := config.Consensus[protocol.ConsensusV36] - addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA") - require.NoError(t, err) v5 := []byte{0x05} - okHeader := Header{ - Sender: addr1, - Fee: basics.MicroAlgos{Raw: 1000}, - LastValid: 105, - FirstValid: 100, - } - usecases := []struct { - tx Transaction - proto config.ConsensusParams - expectedError error + cases := []struct { + ac ApplicationCallTxnFields + cv protocol.ConsensusVersion + expectedError string }{ { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, // creation - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 1, - }, + expectedError: "invalid application OnCompletion (6)", + ac: ApplicationCallTxnFields{ + ApplicationID: 99, + OnCompletion: DeleteApplicationOC + 1, }, - proto: protoV27, - expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", protoV27.MaxExtraAppProgramPages), }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, // creation - ApprovalProgram: []byte(strings.Repeat("X", 1025)), - ClearStateProgram: []byte("Xjunk"), - }, + expectedError: "programs may only be specified during application creation or update", + ac: ApplicationCallTxnFields{ + ApplicationID: 99, + ApprovalProgram: v5, + ClearStateProgram: v5, + OnCompletion: NoOpOC, }, - proto: protoV27, - expectedError: fmt.Errorf("approval program too long. max len 1024 bytes"), }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, // creation - ApprovalProgram: []byte(strings.Repeat("X", 1025)), - ClearStateProgram: []byte("Xjunk"), - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 99, + ApprovalProgram: v5, + ClearStateProgram: v5, + OnCompletion: UpdateApplicationOC, }, - proto: futureProto, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, // creation - ApprovalProgram: []byte(strings.Repeat("X", 1025)), - ClearStateProgram: []byte(strings.Repeat("X", 1025)), + ac: ApplicationCallTxnFields{ + ApplicationID: 0, // creation + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + ExtraProgramPages: 1, }, - proto: futureProto, - expectedError: fmt.Errorf("app programs too long. max total len 2048 bytes"), + cv: cv27, + expectedError: "tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = 0", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, // creation - ApprovalProgram: []byte(strings.Repeat("X", 1025)), - ClearStateProgram: []byte(strings.Repeat("X", 1025)), - ExtraProgramPages: 1, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 0, // creation + ApprovalProgram: []byte(strings.Repeat("X", 1025)), + ClearStateProgram: []byte("Xjunk"), }, - proto: futureProto, + cv: cv27, + expectedError: "approval program too long. max len 1024 bytes", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 1, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 0, // creation + ApprovalProgram: []byte(strings.Repeat("X", 1025)), + ClearStateProgram: []byte("Xjunk"), }, - proto: futureProto, - expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"), - }, - { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 0, - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 4, - }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 0, // creation + ApprovalProgram: []byte(strings.Repeat("X", 1025)), + ClearStateProgram: []byte(strings.Repeat("X", 1025)), }, - proto: futureProto, - expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", futureProto.MaxExtraAppProgramPages), + expectedError: "app programs too long. max total len 2048 bytes", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ForeignApps: []basics.AppIndex{10, 11}, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 0, // creation + ApprovalProgram: []byte(strings.Repeat("X", 1025)), + ClearStateProgram: []byte(strings.Repeat("X", 1025)), + ExtraProgramPages: 1, }, - proto: protoV27, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ForeignApps: []basics.AppIndex{10, 11, 12}, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: [][]byte{ + []byte("write"), }, + GlobalStateSchema: basics.StateSchema{NumByteSlice: 1}, }, - proto: protoV27, - expectedError: fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is 2"), + expectedError: "tx.GlobalStateSchema is immutable", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ForeignApps: []basics.AppIndex{10, 11, 12, 13, 14, 15, 16, 17}, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: [][]byte{ + []byte("write"), }, + LocalStateSchema: basics.StateSchema{NumUint: 1}, }, - proto: futureProto, + expectedError: "tx.LocalStateSchema is immutable", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ForeignAssets: []basics.AssetIndex{14, 15, 16, 17, 18, 19, 20, 21, 22}, + ac: ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + GlobalStateSchema: basics.StateSchema{NumByteSlice: 30, NumUint: 35}, }, - proto: futureProto, - expectedError: fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is 8"), + expectedError: "tx.GlobalStateSchema is too large. 65 > 64", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - Accounts: []basics.Address{{}, {}, {}}, - ForeignApps: []basics.AppIndex{14, 15, 16, 17}, - ForeignAssets: []basics.AssetIndex{14, 15, 16, 17}, + ac: ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + LocalStateSchema: basics.StateSchema{NumUint: 17}, }, - proto: futureProto, - expectedError: fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = 8"), + expectedError: "tx.LocalStateSchema is too large. 17 > 16", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ApprovalProgram: []byte(strings.Repeat("X", 1025)), - ClearStateProgram: []byte(strings.Repeat("X", 1025)), - ExtraProgramPages: 0, - OnCompletion: UpdateApplicationOC, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: [][]byte{ + []byte("write"), }, + ExtraProgramPages: 1, }, - proto: futureProto, - }, - { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - ApprovalProgram: v5, - ClearStateProgram: v5, - ApplicationArgs: [][]byte{ - []byte("write"), - }, - ExtraProgramPages: 1, - OnCompletion: UpdateApplicationOC, + expectedError: "tx.ExtraProgramPages is immutable", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + ExtraProgramPages: 4, }, - proto: protoV28, - expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"), + expectedError: "tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = 3", + cv: cv28, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApprovalProgram: []byte(strings.Repeat("X", 1025)), + ClearStateProgram: []byte(strings.Repeat("X", 1025)), + ExtraProgramPages: 0, + OnCompletion: UpdateApplicationOC, }, - proto: futureProto, - expectedError: fmt.Errorf("tx.Boxes[0].Index is 1. Exceeds len(tx.ForeignApps)"), + cv: protocol.ConsensusFuture, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, - ForeignApps: []basics.AppIndex{1}, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: slices.Repeat([][]byte{[]byte("arg")}, 16), }, - proto: futureProto, }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, - ForeignApps: []basics.AppIndex{1}, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: slices.Repeat([][]byte{[]byte("arg")}, 17), }, - proto: protoV32, - expectedError: fmt.Errorf("tx.Boxes too long, max number of box references is 0"), + expectedError: "tx.ApplicationArgs has too many arguments. 17 > 16", }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: make([]byte, 65)}}, - ForeignApps: []basics.AppIndex{1}, - }, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: [][]byte{make([]byte, 1500), make([]byte, 548)}, }, - proto: futureProto, - expectedError: fmt.Errorf("tx.Boxes[0].Name too long, max len 64 bytes"), }, { - tx: Transaction{ - Type: protocol.ApplicationCallTx, - Header: okHeader, - ApplicationCallTxnFields: ApplicationCallTxnFields{ - ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: make([]byte, 65)}}, - ForeignApps: []basics.AppIndex{1}, + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApplicationArgs: [][]byte{make([]byte, 1501), make([]byte, 548)}, + }, + expectedError: "tx.ApplicationArgs total length is too long. 2049 > 2048", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ForeignApps: []basics.AppIndex{10, 11}, + }, + cv: cv27, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ForeignApps: []basics.AppIndex{10, 11, 12}, + }, + cv: cv27, + expectedError: "tx.ForeignApps too long, max number of foreign apps is 2", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ForeignApps: []basics.AppIndex{10, 11, 12, 13, 14, 15, 16, 17}, + }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Accounts: slices.Repeat([]basics.Address{{}}, 4), + }, + cv: protocol.ConsensusV40, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Accounts: slices.Repeat([]basics.Address{{}}, 5), + }, + cv: protocol.ConsensusV40, + expectedError: "tx.Accounts too long, max number of accounts is 4", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Accounts: slices.Repeat([]basics.Address{{}}, 9), + }, + expectedError: "tx.Accounts too long, max number of accounts is 8", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ForeignAssets: []basics.AssetIndex{14, 15, 16, 17, 18, 19, 20, 21, 22}, + }, + expectedError: "tx.ForeignAssets too long, max number of foreign assets is 8", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Accounts: []basics.Address{{}, {}, {}}, + ForeignApps: []basics.AppIndex{14, 15, 16, 17}, + ForeignAssets: []basics.AssetIndex{14, 15, 16, 17}, + }, + expectedError: "tx references exceed MaxAppTotalTxnReferences = 8", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApprovalProgram: []byte(strings.Repeat("X", 1025)), + ClearStateProgram: []byte(strings.Repeat("X", 1025)), + ExtraProgramPages: 0, + OnCompletion: UpdateApplicationOC, + }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + ApprovalProgram: v5, + ClearStateProgram: v5, + ApplicationArgs: [][]byte{ + []byte("write"), }, + ExtraProgramPages: 1, + OnCompletion: UpdateApplicationOC, + }, + cv: cv28, + expectedError: "tx.ExtraProgramPages is immutable", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, + }, + expectedError: "tx.Boxes[0].Index is 1. Exceeds len(tx.ForeignApps)", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, + ForeignApps: []basics.AppIndex{1}, + }, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, + ForeignApps: []basics.AppIndex{1}, + }, + cv: cv32, + expectedError: "tx.Boxes too long, max number of box references is 0", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: make([]byte, 65)}}, + ForeignApps: []basics.AppIndex{1}, }, - proto: protoV36, - expectedError: nil, + expectedError: "tx.Boxes[0].Name too long, max len 64 bytes", + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: make([]byte, 65)}}, + ForeignApps: []basics.AppIndex{1}, + }, + cv: cv36, }, } - for _, usecase := range usecases { - err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto) - assert.Equal(t, usecase.expectedError, err) + for i, tc := range cases { + name := fmt.Sprintf("i=%d", i) + if tc.expectedError != "" { + name = tc.expectedError + } + t.Run(name, func(t *testing.T) { + cv := tc.cv + if cv == "" { + cv = protocol.ConsensusFuture + } + err := tc.ac.wellFormed(config.Consensus[cv]) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + } + }) } } diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 408eec057f..930c8958af 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -202,10 +202,27 @@ ClearStateProgram fails, and the app's state _is cleared_. ### Resource availability -Smart contracts have limits on the amount of blockchain state they -may examine. Opcodes may only access blockchain resources such as -Accounts, Assets, Boxes, and contract state if the given resource is -_available_. +Smart contracts have limits on the amount of blockchain state they may +examine. These limits are enforced by failing any opcode that +attempts to access a resource unless the resource is +_available_. These resources are: + + * Accounts, which must be available to access their balance, or other + account parameters such as voting details. + * Assets, which must be available to access global asset parameters, such + the as the asset's URL, Name, or privileged addresses. + * Holdings, which must be available to access a particular address's + balance or frozen status for a particular asset. + * Applications, which must be available to read an application's + programs, parameters, or global state. + * Locals, which must be available to read a particular address's local + state for a particular application. + * Boxes, which must be available to read or write a box, designated + by an application and name for the box. + +Resources are _available_ based on the contents of the executing +transaction and, in later versions, the contents of other transactions +in the same group. * A resource in the "foreign array" fields of the ApplicationCall transaction (`txn.Accounts`, `txn.ForeignAssets`, and @@ -214,32 +231,47 @@ _available_. * The `txn.Sender`, `global CurrentApplicationID`, and `global CurrentApplicationAddress` are _available_. - * Prior to v4, all assets were considered _available_ to the - `asset_holding_get` opcode, and all applications were _available_ - to the `app_local_get_ex` opcode. - - * Since v6, any asset or contract that was created earlier in the - same transaction group (whether by a top-level or inner - transaction) is _available_. In addition, any account that is the - associated account of a contract that was created earlier in the - group is _available_. - - * Since v7, the account associated with any contract present in the - `txn.ForeignApplications` field is _available_. - - * Since v9, there is group-level resource sharing. Any resource that - is available in _some_ top-level transaction in a transaction group - is available in _all_ v9 or later application calls in the group, - whether those application calls are top-level or inner. - + * In pre-v4 applications, all holdings are _available_ to the + `asset_holding_get` opcode, and all locals are _available_ to the + `app_local_get_ex` opcode if the *account* of the resource is + _available_. + + * In v6 and later applications, any asset or application that was + created earlier in the same transaction group (whether by a + top-level or inner transaction) is _available_. In addition, any + account that is the associated account of a contract that was + created earlier in the group is _available_. + + * In v7 and later applications, the account associated with any + contract present in the `txn.ForeignApplications` field is + _available_. + + * In v4 and above applications, Holdings and Locals are _available_ + if, both components of the resource are available according to the + above rules. + + * In v9 and later applications, there is group-level resource + sharing. Any resource that is available in _some_ top-level + transaction in a transaction group is available in _all_ v9 or + later application calls in the group, whether those application + calls are top-level or inner. + + * v9 and later applications may use the `txn.Access` list instead of + the foreign arrays. When using `txn.Access` Holdings and Locals are + no longer made available automatically because their components + are. Application accounts are also not made available because of + the availability of their corresponding app. Each resource must be + listed explicitly. However, `txn.Access` allows for the listing of + more resources than the foreign arrays. Listed resources become + available to other (post-v8) applications through group sharing. + * When considering whether an asset holding or application local - state is available by group-level resource sharing, the holding or - local state must be available in a top-level transaction without - considering group sharing. For example, if account A is made - available in one transaction, and asset X is made available in - another, group resource sharing does _not_ make A's X holding - available. - + state is available for group-level resource sharing, the holding or + local state must be available in a top-level transaction based on + pre-v9 rules. For example, if account A is made available in one + transaction, and asset X is made available in another, group + resource sharing does _not_ make A's X holding available. + * Top-level transactions that are not application calls also make resources available to group-level resource sharing. The following resources are made available by other transaction types. @@ -263,10 +295,11 @@ _available_. * A Box is _available_ to an Approval Program if _any_ transaction in - the same group contains a box reference (`txn.Boxes`) that denotes - the box. A box reference contains an index `i`, and name `n`. The - index refers to the `ith` application in the transaction's - ForeignApplications array, with the usual convention that 0 + the same group contains a box reference (in `txn.Boxes` or + `txn.Access`) that denotes the box. A box reference contains an + index `i`, and name `n`. The index refers to the `ith` application + in the transaction's `ForeignApplications` or `Access` array (only + one of which can be used), with the usual convention that 0 indicates the application ID of the app called by that transaction. No box is ever _available_ to a ClearStateProgram. diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 848c345f97..fdf7a0df01 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -188,10 +188,27 @@ ClearStateProgram fails, and the app's state _is cleared_. ### Resource availability -Smart contracts have limits on the amount of blockchain state they -may examine. Opcodes may only access blockchain resources such as -Accounts, Assets, Boxes, and contract state if the given resource is -_available_. +Smart contracts have limits on the amount of blockchain state they may +examine. These limits are enforced by failing any opcode that +attempts to access a resource unless the resource is +_available_. These resources are: + + * Accounts, which must be available to access their balance, or other + account parameters such as voting details. + * Assets, which must be available to access global asset parameters, such + the as the asset's URL, Name, or privileged addresses. + * Holdings, which must be available to access a particular address's + balance or frozen status for a particular asset. + * Applications, which must be available to read an application's + programs, parameters, or global state. + * Locals, which must be available to read a particular address's local + state for a particular application. + * Boxes, which must be available to read or write a box, designated + by an application and name for the box. + +Resources are _available_ based on the contents of the executing +transaction and, in later versions, the contents of other transactions +in the same group. * A resource in the "foreign array" fields of the ApplicationCall transaction (`txn.Accounts`, `txn.ForeignAssets`, and @@ -200,32 +217,47 @@ _available_. * The `txn.Sender`, `global CurrentApplicationID`, and `global CurrentApplicationAddress` are _available_. - * Prior to v4, all assets were considered _available_ to the - `asset_holding_get` opcode, and all applications were _available_ - to the `app_local_get_ex` opcode. - - * Since v6, any asset or contract that was created earlier in the - same transaction group (whether by a top-level or inner - transaction) is _available_. In addition, any account that is the - associated account of a contract that was created earlier in the - group is _available_. - - * Since v7, the account associated with any contract present in the - `txn.ForeignApplications` field is _available_. - - * Since v9, there is group-level resource sharing. Any resource that - is available in _some_ top-level transaction in a transaction group - is available in _all_ v9 or later application calls in the group, - whether those application calls are top-level or inner. - + * In pre-v4 applications, all holdings are _available_ to the + `asset_holding_get` opcode, and all locals are _available_ to the + `app_local_get_ex` opcode if the *account* of the resource is + _available_. + + * In v6 and later applications, any asset or application that was + created earlier in the same transaction group (whether by a + top-level or inner transaction) is _available_. In addition, any + account that is the associated account of a contract that was + created earlier in the group is _available_. + + * In v7 and later applications, the account associated with any + contract present in the `txn.ForeignApplications` field is + _available_. + + * In v4 and above applications, Holdings and Locals are _available_ + if, both components of the resource are available according to the + above rules. + + * In v9 and later applications, there is group-level resource + sharing. Any resource that is available in _some_ top-level + transaction in a transaction group is available in _all_ v9 or + later application calls in the group, whether those application + calls are top-level or inner. + + * v9 and later applications may use the `txn.Access` list instead of + the foreign arrays. When using `txn.Access` Holdings and Locals are + no longer made available automatically because their components + are. Application accounts are also not made available because of + the availability of their corresponding app. Each resource must be + listed explicitly. However, `txn.Access` allows for the listing of + more resources than the foreign arrays. Listed resources become + available to other (post-v8) applications through group sharing. + * When considering whether an asset holding or application local - state is available by group-level resource sharing, the holding or - local state must be available in a top-level transaction without - considering group sharing. For example, if account A is made - available in one transaction, and asset X is made available in - another, group resource sharing does _not_ make A's X holding - available. - + state is available for group-level resource sharing, the holding or + local state must be available in a top-level transaction based on + pre-v9 rules. For example, if account A is made available in one + transaction, and asset X is made available in another, group + resource sharing does _not_ make A's X holding available. + * Top-level transactions that are not application calls also make resources available to group-level resource sharing. The following resources are made available by other transaction types. @@ -249,10 +281,11 @@ _available_. * A Box is _available_ to an Approval Program if _any_ transaction in - the same group contains a box reference (`txn.Boxes`) that denotes - the box. A box reference contains an index `i`, and name `n`. The - index refers to the `ith` application in the transaction's - ForeignApplications array, with the usual convention that 0 + the same group contains a box reference (in `txn.Boxes` or + `txn.Access`) that denotes the box. A box reference contains an + index `i`, and name `n`. The index refers to the `ith` application + in the transaction's `ForeignApplications` or `Access` array (only + one of which can be used), with the usual convention that 0 indicates the application ID of the app called by that transaction. No box is ever _available_ to a ClearStateProgram. diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index fb9ee6aa09..3a85951292 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -44,7 +44,7 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS return nil, false, fmt.Errorf("boxes may not be accessed from ClearState program") } - dirty, ok := cx.available.boxes[BoxRef{cx.appID, name}] + dirty, ok := cx.available.boxes[basics.BoxRef{App: cx.appID, Name: name}] if !ok && cx.UnnamedResources != nil { ok = cx.UnnamedResources.AvailableBox(cx.appID, name, operation, createSize) } @@ -98,7 +98,7 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS case BoxReadOperation: /* nothing to do */ } - cx.available.boxes[BoxRef{cx.appID, name}] = dirty + cx.available.boxes[basics.BoxRef{App: cx.appID, Name: name}] = dirty if cx.available.dirtyBytes > cx.ioBudget { return nil, false, fmt.Errorf("write budget (%d) exceeded %d", cx.ioBudget, cx.available.dirtyBytes) diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 0b6c604b8e..bfe23b9c54 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -73,45 +73,102 @@ func TestBoxNewDel(t *testing.T) { func TestBoxNewBad(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - - ep, txn, ledger := MakeSampleEnv() - - ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - TestApp(t, `byte "self"; int 999; box_create`, ep, "write budget") - - // In test proto, you get 100 I/O budget per boxref, and 1000 is the - // absolute biggest box. - ten := [10]transactions.BoxRef{} - txn.Boxes = append(txn.Boxes, ten[:]...) // write budget is now 11*100 = 1100 - TestApp(t, `byte "self"; int 999; box_create`, ep) - TestApp(t, `byte "self"; int 1000; box_resize; int 1`, ep) - TestApp(t, `byte "self"; int 1001; box_resize; int 1`, ep, "box size too large") - ledger.DelBoxes(888, "self") - TestApp(t, `byte "self"; int 1000; box_create`, ep) - ledger.DelBoxes(888, "self") - TestApp(t, `byte "self"; int 1001; box_create`, ep, "box size too large") - TestApp(t, `byte "unknown"; int 1000; box_create`, ep, "invalid Box reference") - - long := strings.Repeat("x", 65) - txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} - TestApp(t, NoTrack(fmt.Sprintf(`byte "%s"; int 1000; box_create`, long)), ep, "name too long") - - txn.Boxes = []transactions.BoxRef{{Name: []byte("")}} // irrelevant, zero check comes first anyway - TestApp(t, NoTrack(`byte ""; int 1000; box_create`), ep, "zero length") + for _, access := range []bool{false, true} { + t.Run(fmt.Sprintf("access=%t", access), func(t *testing.T) { + t.Parallel() + ep, txn, ledger := MakeSampleEnv() + // txn.Txn.Boxes contains (0,"self"), (0,"other") + if access { + ConvertEPToAccess(ep, false) + // now .Boxes is nil, .Access contains what was in .Boxes + } + ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + TestApp(t, `byte "self"; int 200; box_create`, ep) + ledger.DelBoxes(888, "self") + TestApp(t, `byte "self"; int 201; box_create`, ep, "write budget") // 2 boxes gives 200 budget + + // In test proto, you get 100 I/O budget per boxref, and 1000 is the + // absolute biggest box. So that you get budget for boxes named but + // unused, and for empties. + if access { + // expand write budget + two := [2]transactions.ResourceRef{} + txn.Access = append(txn.Access, two[:]...) // write budget is now 11*100 = 1100 + x := transactions.ResourceRef{Box: transactions.BoxRef{Index: 0, Name: []byte{0x01}}} + for range 2 { + txn.Access = append(txn.Access, x) + } + } else { + two := [2]transactions.BoxRef{} + txn.Boxes = append(txn.Boxes, two[:]...) // write budget is now 11*100 = 1100 + x := transactions.BoxRef{Index: 0, Name: []byte{0x01}} + for range 2 { + txn.Boxes = append(txn.Boxes, x) + } + } + // write budget is now (2 original + 2 empties + 2 x's)*100 = 600 + TestApp(t, `byte "self"; int 600; box_create`, ep) + ledger.DelBoxes(888, "self") + TestApp(t, `byte "self"; int 601; box_create`, ep, "write budget") // 2 boxes gives 200 budget + + // add more budget to test max box length + if access { + // expand write budget + five := [5]transactions.ResourceRef{} + txn.Access = append(txn.Access, five[:]...) // write budget is now 11*100 = 1100 + } else { + five := [5]transactions.BoxRef{} + txn.Boxes = append(txn.Boxes, five[:]...) // write budget is now 11*100 = 1100 + } + // write budget is up to 1100, but we can't make more that 1000 + + TestApp(t, `byte "self"; int 999; box_create`, ep) + TestApp(t, `byte "self"; int 1000; box_resize; int 1`, ep) + TestApp(t, `byte "self"; int 1001; box_resize; int 1`, ep, "box size too large") + ledger.DelBoxes(888, "self") + TestApp(t, `byte "self"; int 1000; box_create`, ep) + ledger.DelBoxes(888, "self") + TestApp(t, `byte "self"; int 1001; box_create`, ep, "box size too large") + + TestApp(t, `byte "unknown"; int 1000; box_create`, ep, "invalid Box reference") + + long := strings.Repeat("x", 65) + if access { + txn.Access = []transactions.ResourceRef{{Box: transactions.BoxRef{Name: []byte(long)}}} + } else { + txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} + } + TestApp(t, NoTrack(fmt.Sprintf(`byte "%s"; int 1000; box_create`, long)), ep, "name too long") + + // irrelevant, zero check comes first anyway + if access { + txn.Access = []transactions.ResourceRef{{Box: transactions.BoxRef{Name: []byte("")}}} + } else { + txn.Boxes = []transactions.BoxRef{{Name: []byte("")}} + } + TestApp(t, NoTrack(`byte ""; int 1000; box_create`), ep, "zero length") + }) + } } func TestBoxReadWrite(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - ep, txn, ledger := MakeSampleEnv() + for _, access := range []bool{false, true} { + t.Run(fmt.Sprintf("access=%t", access), func(t *testing.T) { + t.Parallel() + ep, txn, ledger := MakeSampleEnv() + // txn.Txn.Boxes contains (0,"self"), (0,"other") + if access { + ConvertEPToAccess(ep, false) + // now .Boxes is nil, .Access contains what was in .Boxes + } - ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - // extract some bytes until past the end, confirm the begin as zeros, and - // when it fails. - TestApp(t, `byte "self"; int 4; box_create; assert + ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + // extract some bytes until past the end, confirm the begin as zeros, and + // when it fails. + TestApp(t, `byte "self"; int 4; box_create; assert byte "self"; int 1; int 2; box_extract; byte 0x0000; ==; assert; byte "self"; int 1; int 3; box_extract; @@ -120,36 +177,38 @@ func TestBoxReadWrite(t *testing.T) { byte 0x00000000; ==; assert; int 1`, ep) - TestApp(t, `byte "self"; int 1; int 4; box_extract; + TestApp(t, `byte "self"; int 1; int 4; box_extract; byte 0x00000000; ==`, ep, "extraction end 5") - // Replace some bytes until past the end, confirm when it fails. - TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace; + // Replace some bytes until past the end, confirm when it fails. + TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x00303100; ==`, ep) - TestApp(t, `byte "self"; int 1; byte 0x303132; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x303132; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x00303132; ==`, ep) - TestApp(t, `byte "self"; int 1; byte 0x30313233; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x30313233; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x0030313233; ==`, ep, "replacement end 5") - // Replace with different byte in different place. - TestApp(t, `byte "self"; int 0; byte 0x4444; box_replace; + // Replace with different byte in different place. + TestApp(t, `byte "self"; int 0; byte 0x4444; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x44443132; ==`, ep) - // All bow down to the God of code coverage! - ledger.DelBoxes(888, "self") - TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace`, ep, - "no such box") - TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep, - "invalid Box reference") - - TestApp(t, `byte "self"; int 1; int 2; byte 0x3031; box_splice`, ep, - "no such box") - TestApp(t, `byte "junk"; int 1; int 2; byte 0x3031; box_splice`, ep, - "invalid Box reference") + // All bow down to the God of code coverage! + ledger.DelBoxes(888, "self") + TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace`, ep, + "no such box") + TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep, + "invalid Box reference") + + TestApp(t, `byte "self"; int 1; int 2; byte 0x3031; box_splice`, ep, + "no such box") + TestApp(t, `byte "junk"; int 1; int 2; byte 0x3031; box_splice`, ep, + "invalid Box reference") + }) + } } func TestBoxSplice(t *testing.T) { @@ -360,43 +419,68 @@ func TestBoxAvailability(t *testing.T) { func TestBoxReadBudget(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() appID := basics.AppIndex(888) appAddr := appID.Address() - ep, txn, ledger := MakeSampleEnv() - ledger.NewApp(basics.Address{}, appID, basics.AppParams{}) - - // Sample txn has two box refs, so read budget is 2*100 - - ledger.NewBox(appID, "self", make([]byte, 100), appAddr) - ledger.NewBox(appID, "other", make([]byte, 100), appAddr) - ledger.NewBox(appID, "third", make([]byte, 100), appAddr) - - // Right at budget - TestApp(t, `byte "self"; box_len; assert; byte "other"; box_len; assert; ==`, ep) - - // With three box refs, read budget is now 3*100 - txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("third")}) - TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep) - - // Increase "third" box size to 101 - ledger.DelBox(appID, "third", appAddr) - ledger.NewBox(appID, "third", make([]byte, 101), appAddr) - - // Budget exceeded - TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep, "box read budget (300) exceeded") - // Still exceeded if we don't touch the boxes - TestApp(t, `int 1`, ep, "box read budget (300) exceeded") - - // Still exceeded with one box ref - txn.Boxes = txn.Boxes[2:] - TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep, "box read budget (100) exceeded") - - // But not with two - txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) - TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep) + for _, access := range []bool{false, true} { + t.Run(fmt.Sprintf("access=%t", access), func(t *testing.T) { + t.Parallel() + ep, txn, ledger := MakeSampleEnv() + // txn.Txn.Boxes contains (0,"self"), (0,"other") + if access { + ConvertEPToAccess(ep, false) + // now .Boxes is nil, .Access contains what was in .Boxes + } + + ledger.NewApp(basics.Address{}, appID, basics.AppParams{}) + + // Sample txn has two box refs, so read budget is 2*100 + + ledger.NewBox(appID, "self", make([]byte, 100), appAddr) + ledger.NewBox(appID, "other", make([]byte, 100), appAddr) + ledger.NewBox(appID, "third", make([]byte, 100), appAddr) + + // Right at budget + TestApp(t, `byte "self"; box_len; assert; byte "other"; box_len; assert; ==`, ep) + + // With three box refs, read budget is now 3*100 + if access { + txn.Access = append(txn.Access, transactions.ResourceRef{ + Box: transactions.BoxRef{Name: []byte("third")}, + }) + } else { + txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("third")}) + } + TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep) + + // Increase "third" box size to 101 + ledger.DelBox(appID, "third", appAddr) + ledger.NewBox(appID, "third", make([]byte, 101), appAddr) + + // Budget exceeded + TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep, "box read budget (300) exceeded") + // Still exceeded if we don't touch the boxes + TestApp(t, `int 1`, ep, "box read budget (300) exceeded") + + // Still exceeded with one box ref (remove the original box refs) + if access { + txn.Access[len(txn.Access)-3] = txn.Access[len(txn.Access)-1] + txn.Access = txn.Access[:len(txn.Access)-2] + } else { + txn.Boxes = txn.Boxes[2:] + } + TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep, "box read budget (100) exceeded") + + // But not with a second empty ref + if access { + txn.Access = append(txn.Access, transactions.ResourceRef{}) + } else { + txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) + } + TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep) + }) + } } func TestBoxWriteBudget(t *testing.T) { @@ -529,33 +613,52 @@ func TestBoxRepeatedCreate(t *testing.T) { func TestIOBudgetGrow(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - - ep, txn, ledger := MakeSampleEnv() - ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) - ledger.CreateBox(888, "self", 101) - ledger.CreateBox(888, "other", 101) - TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + for _, access := range []bool{false, true} { + t.Run(fmt.Sprintf("access=%t", access), func(t *testing.T) { + t.Parallel() + ep, txn, ledger := MakeSampleEnv() + // txn.Txn.Boxes contains (0,"self"), (0,"other") + if access { + ConvertEPToAccess(ep, false) + // now .Boxes is nil, .Access contains what was in .Boxes + } + ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) + ledger.CreateBox(888, "self", 101) + ledger.CreateBox(888, "other", 101) + + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep, "read budget (200) exceeded") - txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) - // Since we added an empty BoxRef, we can read > 200. - TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; + if access { + txn.Access = append(txn.Access, transactions.ResourceRef{}) + } else { + txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) + } + // Since we added an empty BoxRef, we can read > 200. + TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; byte "other"; int 1; int 7; box_extract; pop; int 1`, ep) - // Add write, for that matter - TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + // Add write, for that matter + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep) - txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("another")}) + if access { + txn.Access = append(txn.Access, transactions.ResourceRef{ + Box: transactions.BoxRef{Name: []byte("another")}, + }) + } else { + txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("another")}) + } - // Here we read 202, and write a very different 350 (since we now have 4 brs) - TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; + // Here we read 202, and write a very different 350 (since we now have 4 brs) + TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; byte "other"; int 1; int 7; box_extract; pop; byte "another"; int 350; box_create`, ep) + }) + } } func TestConveniences(t *testing.T) { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index e058a3687c..f29b2b1a03 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -41,6 +41,7 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" ) // The constants below control opcode evaluation and MAY NOT be changed without @@ -275,12 +276,6 @@ type LedgerForLogic interface { Counter() uint64 } -// BoxRef is the "hydrated" form of a transactions.BoxRef - it has the actual app id, not an index -type BoxRef struct { - App basics.AppIndex - Name string -} - // UnnamedResourcePolicy is an interface that defines the policy for allowing unnamed resources. // This should only be used during simulation or debugging. type UnnamedResourcePolicy interface { @@ -496,7 +491,7 @@ func (ep *EvalParams) computeAvailability() *resources { sharedApps: make(map[basics.AppIndex]struct{}), sharedHoldings: make(map[ledgercore.AccountAsset]struct{}), sharedLocals: make(map[ledgercore.AccountApp]struct{}), - boxes: make(map[BoxRef]bool), + boxes: make(map[basics.BoxRef]bool), } for i := range ep.TxnGroup { available.fill(&ep.TxnGroup[i].Txn, ep) @@ -1076,31 +1071,18 @@ func (cx *EvalContext) evalStates() []evalState { // should be located outside of the evalState, with the PC. var stack []any if cx.groupIndex == i { - stack = convertSlice(cx.Stack, func(sv stackValue) any { - return sv.asAny() - }) + stack = util.Map(cx.Stack, stackValue.asAny) } states[i] = evalState{ Scratch: scratchAsAny, Stack: stack, - Logs: convertSlice(cx.TxnGroup[i].EvalDelta.Logs, func(s string) []byte { return []byte(s) }), + Logs: util.Map(cx.TxnGroup[i].EvalDelta.Logs, func(s string) []byte { return []byte(s) }), } } return states } -func convertSlice[X any, Y any](input []X, fn func(X) Y) []Y { - if input == nil { - return nil - } - output := make([]Y, len(input)) - for i := range input { - output[i] = fn(input[i]) - } - return output -} - // EvalContract executes stateful program as the gi'th transaction in params func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (bool, *EvalContext, error) { if params.Ledger == nil { @@ -1129,7 +1111,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if cx.Proto.IsolateClearState && cx.txn.Txn.OnCompletion == transactions.ClearStateOC { if cx.PooledApplicationBudget != nil && *cx.PooledApplicationBudget < cx.Proto.MaxAppProgramCost { - return false, nil, fmt.Errorf("Attempted ClearState execution with low OpcodeBudget %d", *cx.PooledApplicationBudget) + return false, nil, fmt.Errorf("attempted ClearState execution with low OpcodeBudget %d", *cx.PooledApplicationBudget) } } @@ -1142,7 +1124,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam // make any "0 index" box refs available now that we have an appID. for _, br := range cx.txn.Txn.Boxes { if br.Index == 0 { - cx.EvalParams.available.boxes[BoxRef{cx.appID, string(br.Name)}] = false + cx.EvalParams.available.boxes[basics.BoxRef{App: cx.appID, Name: string(br.Name)}] = false } } // and add the appID to `createdApps` @@ -1154,11 +1136,17 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam // Check the I/O budget for reading if this is the first top-level app call if cx.caller == nil && !cx.readBudgetChecked { - boxRefCount := uint64(0) // Intentionally counts duplicates + bumps := uint64(0) // Intentionally counts duplicates for _, tx := range cx.TxnGroup { - boxRefCount += uint64(len(tx.Txn.Boxes)) + bumps += uint64(len(tx.Txn.Boxes)) + for _, rr := range tx.Txn.Access { + // A box or an empty ref is an io quota bump + if !rr.Box.Empty() || rr.Empty() { + bumps++ + } + } } - cx.ioBudget = boxRefCount * cx.Proto.BytesPerBoxReference + cx.ioBudget = bumps * cx.Proto.BytesPerBoxReference used := uint64(0) for br := range cx.available.boxes { @@ -1358,18 +1346,18 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { // static checks and reject programs that are invalid. Prior to v4, // these static checks include a cost estimate that must be low enough // (controlled by params.Proto). -func CheckContract(program []byte, params *EvalParams) error { - return check(program, params, ModeApp) +func CheckContract(program []byte, gi int, params *EvalParams) error { + return check(program, gi, params, ModeApp) } // CheckSignature should be faster than EvalSignature. It can perform static // checks and reject programs that are invalid. Prior to v4, these static checks // include a cost estimate that must be low enough (controlled by params.Proto). func CheckSignature(gi int, params *EvalParams) error { - return check(params.TxnGroup[gi].Lsig.Logic, params, ModeSig) + return check(params.TxnGroup[gi].Lsig.Logic, gi, params, ModeSig) } -func check(program []byte, params *EvalParams, mode RunMode) (err error) { +func check(program []byte, gi int, params *EvalParams, mode RunMode) (err error) { defer func() { if x := recover(); x != nil { buf := make([]byte, 16*1024) @@ -1391,6 +1379,7 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) { cx.runMode = mode cx.branchTargets = make([]bool, len(program)+1) // teal v2 allowed jumping to the end of the prog cx.instructionStarts = make([]bool, len(program)+1) + cx.txn = ¶ms.TxnGroup[gi] if err := cx.begin(program); err != nil { return err @@ -1432,6 +1421,16 @@ func (cx *EvalContext) begin(program []byte) error { if version > cx.Proto.LogicSigVersion { return fmt.Errorf("program version %d greater than protocol supported version %d", version, cx.Proto.LogicSigVersion) } + // We disallow pre-sharedResources programs with tx.Access for the same + // reason that we don't allow resource sharing to happen for low version + // programs. We don't want programs to have access to unexpected + // things. Worse, we don't want to deal with the potentially new sitation + // that a preSharing program could have access to an account and an ASA, but + // not the corresponding holding. We DO allow logicsigs, because they can't + // access state anyway. + if version < sharedResourcesVersion && cx.runMode == ModeApp && len(cx.txn.Txn.Access) > 0 { + return fmt.Errorf("pre-sharedResources program cannot be invoked with tx.Access") + } cx.version = version cx.pc = vlen @@ -1986,7 +1985,7 @@ func opBtoi(cx *EvalContext) error { last := len(cx.Stack) - 1 ibytes := cx.Stack[last].Bytes if len(ibytes) > 8 { - return fmt.Errorf("btoi arg too long, got [%d]bytes", len(ibytes)) + return fmt.Errorf("btoi arg too long, got %d bytes", len(ibytes)) } value := uint64(0) for _, b := range ibytes { @@ -4239,7 +4238,7 @@ func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uin // created earlier in the group, or because of group sharing) ok := cx.availableAccount(addr) if !ok { - return addr, 0, fmt.Errorf("invalid Account reference %s", addr) + return addr, 0, fmt.Errorf("unavailable Account %s", addr) } // available, but not in txn.Accounts. Return 1 higher to signal. return addr, uint64(len(cx.txn.Txn.Accounts) + 1), nil @@ -4535,7 +4534,7 @@ func opAppLocalPut(cx *EvalContext) error { // programs. The test here is to ensure that we didn't get access to the // address from another txn, but don't have access to the local state. if cx.version >= sharedResourcesVersion && !cx.allowsLocals(addr, cx.appID) { - return fmt.Errorf("unavailable Local State %s x %d", addr, cx.appID) + return fmt.Errorf("unavailable Local State %d+%s", cx.appID, addr) } // if writing the same value, don't record in EvalDelta, matching ledger @@ -4630,7 +4629,7 @@ func opAppLocalDel(cx *EvalContext) error { // programs. The test here is to ensure that we didn't get access to the // address from another txn, but don't have access to the local state. if cx.version >= sharedResourcesVersion && !cx.allowsLocals(addr, cx.appID) { - return fmt.Errorf("unavailable Local State %s x %d", addr, cx.appID) + return fmt.Errorf("unavailable Local State %d+%s", cx.appID, addr) } // if deleting a non-existent value, don't record in EvalDelta, matching @@ -4706,9 +4705,9 @@ func (cx *EvalContext) appReference(ref uint64, foreign bool) (aid basics.AppInd if foreign { // In old versions, a foreign reference must be an index in ForeignApps or 0 if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { - return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil + return cx.txn.Txn.ForeignApps[ref-1], nil } - return 0, fmt.Errorf("App index %d beyond txn.ForeignApps", ref) + return 0, fmt.Errorf("%d is not a valid foreign app slot", ref) } // Otherwise it's direct return basics.AppIndex(ref), nil @@ -4738,8 +4737,12 @@ func (cx *EvalContext) resolveApp(ref uint64) (aid basics.AppIndex, err error) { // given to anyone who cares about semantics in the first few rounds of // a new network - don't use indexes for references, use the App ID if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { - return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil + return cx.txn.Txn.ForeignApps[ref-1], nil + } + if ref > 0 && ref-1 < uint64(len(cx.txn.Txn.Access)) && cx.txn.Txn.Access[ref-1].App != 0 { + return cx.txn.Txn.Access[ref-1].App, nil } + return 0, fmt.Errorf("unavailable App %d", ref) } @@ -4761,21 +4764,23 @@ func (cx *EvalContext) localsReference(account stackValue, ref uint64) (basics.A } } - // Do an extra check to give a better error. The app is definitely - // available. If the addr is too, then the trouble is they must have - // come from different transactions, and the HOLDING is the problem. + // Do an extra check to give a better error, which also allows the + // UnnamedResources code to notice that the account must be available as + // well. acctOK := cx.availableAccount(addr) + localsErr := fmt.Errorf("unavailable Local State %d+%s", aid, addr) + switch { case err != nil && acctOK: - // do nothing, err contains the an Asset specific problem + // do nothing, err contains an App specific problem case err == nil && acctOK: // although both are available, the LOCALS are not - err = fmt.Errorf("unavailable Local State %s x %d", addr, aid) + err = localsErr case err != nil && !acctOK: - err = fmt.Errorf("unavailable Account %s, %w", addr, err) + err = fmt.Errorf("unavailable Account %s, %w, %w", addr, err, localsErr) case err == nil && !acctOK: - err = fmt.Errorf("unavailable Account %s", addr) + err = fmt.Errorf("unavailable Account %s, %w", addr, localsErr) } return basics.Address{}, 0, 0, err @@ -4791,6 +4796,7 @@ func (cx *EvalContext) localsReference(account stackValue, ref uint64) (basics.A if err != nil { return basics.Address{}, 0, 0, err } + return addr, app, addrIdx, nil } @@ -4813,9 +4819,9 @@ func (cx *EvalContext) assetReference(ref uint64, foreign bool) (aid basics.Asse if foreign { // In old versions, a foreign reference must be an index in ForeignAssets if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { - return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil + return cx.txn.Txn.ForeignAssets[ref], nil } - return 0, fmt.Errorf("Asset index %d beyond txn.ForeignAssets", ref) + return 0, fmt.Errorf("%d is not a valid foreign asset slot", ref) } // Otherwise it's direct return basics.AssetIndex(ref), nil @@ -4823,9 +4829,8 @@ func (cx *EvalContext) assetReference(ref uint64, foreign bool) (aid basics.Asse const lastForbiddenResource = 255 -// resolveAsset figures out what Asset an integer is referring to, considering 0 as -// current app first, then uses the integer as is if it is an availableAsset, then -// tries to perform a slot lookup. +// resolveAsset figures out what Asset an integer is referring to, checking if +// the integer is an availableAsset, then tries to perform a slot lookup. func (cx *EvalContext) resolveAsset(ref uint64) (aid basics.AssetIndex, err error) { if cx.Proto.AppForbidLowResources { defer func() { @@ -4843,7 +4848,10 @@ func (cx *EvalContext) resolveAsset(ref uint64) (aid basics.AssetIndex, err erro // given to anyone who cares about semantics in the first few rounds of // a new network - don't use indexes for references, use the Asset ID if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { - return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil + return cx.txn.Txn.ForeignAssets[ref], nil + } + if ref > 0 && ref-1 < uint64(len(cx.txn.Txn.Access)) && cx.txn.Txn.Access[ref-1].Asset != 0 { + return cx.txn.Txn.Access[ref-1].Asset, nil } return 0, fmt.Errorf("unavailable Asset %d", ref) } @@ -4868,10 +4876,10 @@ func (cx *EvalContext) holdingReference(account stackValue, ref uint64) (basics. acctOK := cx.availableAccount(addr) switch { case err != nil && acctOK: - // do nothing, err contains the an Asset specific problem + // do nothing, err contains an Asset specific problem case err == nil && acctOK: // although both are available, the HOLDING is not - err = fmt.Errorf("unavailable Holding %s x %d", addr, aid) + err = fmt.Errorf("unavailable Holding %d+%s", aid, addr) case err != nil && !acctOK: err = fmt.Errorf("unavailable Account %s, %w", addr, err) case err == nil && !acctOK: @@ -5200,7 +5208,7 @@ func (cx *EvalContext) assignAsset(sv stackValue) (basics.AssetIndex, error) { return aid, nil } - return 0, fmt.Errorf("unavailable Asset %d", aid) + return 0, fmt.Errorf("unavailable Asset %d during assignment %v", aid, cx.available) } // availableAsset determines whether an asset is "available". Before @@ -5211,7 +5219,10 @@ func (cx *EvalContext) assignAsset(sv stackValue) (basics.AssetIndex, error) { // transaction (axfer,acfg,afrz), but not for holding lookups or assignments to // an inner static array. func (cx *EvalContext) availableAsset(aid basics.AssetIndex) bool { - // Ensure that aid is in Foreign Assets + // Check if aid is in an access array + if slices.ContainsFunc(cx.txn.Txn.Access, func(rr transactions.ResourceRef) bool { return rr.Asset == aid }) { + return true + } if slices.Contains(cx.txn.Txn.ForeignAssets, aid) { return true } @@ -5253,7 +5264,10 @@ func (cx *EvalContext) assignApp(sv stackValue) (basics.AppIndex, error) { } func (cx *EvalContext) availableApp(aid basics.AppIndex) bool { - // Ensure that aid is in Foreign Apps + // Check if aid is in an access array + if slices.ContainsFunc(cx.txn.Txn.Access, func(rr transactions.ResourceRef) bool { return rr.App == aid }) { + return true + } if slices.Contains(cx.txn.Txn.ForeignApps, aid) { return true } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 4f7f0d3aea..40d380c36c 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -19,6 +19,7 @@ package logic import ( "encoding/hex" "fmt" + "slices" "strconv" "strings" "testing" @@ -32,6 +33,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util" ) func makeApp(li uint64, lb uint64, gi uint64, gb uint64) basics.AppParams { @@ -72,6 +74,92 @@ func makeOldAndNewEnv(version uint64) (*EvalParams, *EvalParams, *Ledger) { return old, new, sharedLedger } +// Converts ep in place such that only tx.Access is used, not foreign arrays +func convertEPToAccess(ep *EvalParams, includeCrossProducts bool) { + for i := range ep.TxnGroup { + ep.TxnGroup[i].Txn = convertTxnToAccess(ep.TxnGroup[i].Txn, includeCrossProducts) + } +} + +// Returns a copy of `txn` with Foreign arrays nil'd out and replaced by equivalent Access list. +func convertTxnToAccess(txn transactions.Transaction, includeCrossProducts bool) transactions.Transaction { + for _, acct := range txn.Accounts { + txn.Access = append(txn.Access, transactions.ResourceRef{ + Address: acct, + }) + } + + for _, asset := range txn.ForeignAssets { + // Add each foreign asset + txn.Access = append(txn.Access, transactions.ResourceRef{ + Asset: asset, + }) + if includeCrossProducts { + index := len(txn.Access) + // Add the holding of the Sender + txn.Access = append(txn.Access, transactions.ResourceRef{ + Holding: transactions.HoldingRef{ + Address: 0, // Sender + Asset: uint64(index), + }, + }) + // Add holding for each added account + for i := range txn.Accounts { + txn.Access = append(txn.Access, transactions.ResourceRef{ + Holding: transactions.HoldingRef{ + Address: uint64(i + 1), + Asset: uint64(index), + }, + }) + } + } + } + + for _, app := range txn.ForeignApps { + txn.Access = append(txn.Access, transactions.ResourceRef{ + App: app, + }) + if includeCrossProducts { + index := len(txn.Access) + txn.Access = append(txn.Access, transactions.ResourceRef{ + Locals: transactions.LocalsRef{ + Address: 0, // Sender + App: uint64(index), + }, + }) + for i := range txn.Accounts { + txn.Access = append(txn.Access, transactions.ResourceRef{ + Locals: transactions.LocalsRef{ + Address: uint64(i + 1), + App: uint64(index), + }, + }) + } + } + } + + for _, br := range txn.Boxes { + index := br.Index + if index != 0 { + app := txn.ForeignApps[index-1] + index = uint64(slices.IndexFunc(txn.Access, func(rr transactions.ResourceRef) bool { + return rr.App == app + })) + } + txn.Access = append(txn.Access, transactions.ResourceRef{ + Box: transactions.BoxRef{ + Index: index, + Name: br.Name, + }, + }) + } + txn.Accounts = nil + txn.ForeignAssets = nil + txn.ForeignApps = nil + txn.Boxes = nil + return txn +} + func (r *resources) String() string { sb := strings.Builder{} if len(r.createdAsas) > 0 { @@ -536,7 +624,7 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * ep.Trace = &strings.Builder{} - err := CheckContract(program, ep) + err := CheckContract(program, gi, ep) if checkProblem == "" { require.NoError(t, err, "Error in CheckContract %v", ep.Trace) } else { @@ -784,7 +872,7 @@ byte "ALGO" testApp(t, text, now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02\"", -1), now, - "invalid Account reference") + "unavailable Account") // check reading state of other app ledger.NewApp(now.TxnGroup[0].Txn.Sender, 56, basics.AppParams{}) @@ -818,7 +906,7 @@ byte "ALGO" exp(3, "app_local_get arg 0 wanted type uint64...")) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02\"", -1), now, - "invalid Account reference") + "unavailable Account") // check app_local_get default value text = `int 0 // account idx @@ -847,7 +935,7 @@ int 100; byte 0x0201; == // types mismatch so this will fail _, err := testApp(t, badsource, nil, "cannot compare") attrs := basics.Attributes(err) zeros := [256]int{} - scratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + scratch := util.Map(zeros[:], func(i int) any { return uint64(i) }) scratch[10] = uint64(5) scratch[15] = []byte{0x01, 0x02, 0x03, 0x00} require.Equal(t, map[string]any{ @@ -871,7 +959,7 @@ int 4; store 2 // store an int byte "jj"; store 3 // store a bytes int 1 ` - gscratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + gscratch := util.Map(zeros[:], func(i int) any { return uint64(i) }) gscratch[2] = uint64(4) gscratch[3] = []byte("jj") @@ -996,7 +1084,7 @@ byte "ALGO" // check that actual app id ok instead of indirect reference text = `int 100; txn ApplicationArgs 0; app_global_get_ex; int 1; ==; assert; byte "ALGO"; ==` testApp(t, text, now) - testApp(t, text, pre, "App index 100 beyond") // but not in old teal + testApp(t, text, pre, "100 is not a valid foreign app slot") // but not in old teal // check app_global_get default value text = "byte 0x414c474f55; app_global_get; int 0; ==" @@ -1034,320 +1122,301 @@ int 4141 0, 100, now) } -const assetsTestTemplate = `int 0//account +const assetsTestTemplate = ` +int 0//account int 55 -asset_holding_get AssetBalance -! -bnz error -int 123 -== +asset_holding_get AssetBalance; assert +int 123; ==; assert + int 0//account int 55 -asset_holding_get AssetFrozen -! -bnz error -int 1 -== -&& -int 0//params -asset_params_get AssetTotal -! -bnz error -int 1000 -== -&& -int 0//params -asset_params_get AssetDecimals -! -bnz error -int 2 -== -&& -int 0//params -asset_params_get AssetDefaultFrozen -! -bnz error -int 0 -== -&& -int 0//params -asset_params_get AssetUnitName -! -bnz error -byte "ALGO" -== -&& -int 0//params -asset_params_get AssetName -! -bnz error -len -int 0 -== -&& -int 0//params -asset_params_get AssetURL -! -bnz error -txna ApplicationArgs 0 -== -&& -int 0//params -asset_params_get AssetMetadataHash -! -bnz error -byte 0x0000000000000000000000000000000000000000000000000000000000000000 -== -&& -int 0//params -asset_params_get AssetManager -! -bnz error -txna Accounts 0 -== -&& -int 0//params -asset_params_get AssetReserve -! -bnz error -txna Accounts 1 -== -&& -int 0//params -asset_params_get AssetFreeze -! -bnz error -txna Accounts 1 -== -&& -int 0//params -asset_params_get AssetClawback -! -bnz error -txna Accounts 1 -== -&& -bnz ok -error: -err -ok: +asset_holding_get AssetFrozen; assert +int 1; ==; assert + +int 0//asset +asset_params_get AssetTotal; assert +int 1000; ==; assert + +int 0//asset +asset_params_get AssetDecimals; assert +int 2; ==; assert + +int 0//asset +asset_params_get AssetDefaultFrozen; assert +int 0; ==; assert + +int 0//asset +asset_params_get AssetUnitName; assert +byte "ALGO"; ==; assert + +int 0//asset +asset_params_get AssetName; assert +len; int 0; ==; assert + +int 0//asset +asset_params_get AssetURL; assert +txna ApplicationArgs 0; ==; assert + +int 0//asset +asset_params_get AssetMetadataHash; assert +byte 0x0000000000000000000000000000000000000000000000000000000000000000; ==; assert + +int 0//asset +asset_params_get AssetManager; assert +txna Accounts 0; ==; assert // even with tx.Access, this means Sender + +int 0//asset +asset_params_get AssetReserve; assert +txna Accounts 1; ==; assert // this won't work when we use tx.Access, see substitute() call + +int 0//asset +asset_params_get AssetFreeze; assert +txna Accounts 1; ==; assert + +int 0//asset +asset_params_get AssetClawback; assert +txna Accounts 1; ==; assert + %s + int 1 ` +// v5extras adds test for AssetCreator, new in v5 const v5extras = ` -int 0//params -asset_params_get AssetCreator -pop -txn Sender -== -assert +int 0//asset +asset_params_get AssetCreator; assert +txn Sender; ==; assert ` +func substitute(s string, replacements map[string]string) string { + for old, new := range replacements { + s = strings.ReplaceAll(s, old, new) + } + return s +} + func TestAssets(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() tests := map[uint64]string{ - 4: fmt.Sprintf(assetsTestTemplate, ""), - 5: fmt.Sprintf(assetsTestTemplate, v5extras), - } - - for v, source := range tests { - testAssetsByVersion(t, source, v) + 4: fmt.Sprintf(assetsTestTemplate, ""), + 5: fmt.Sprintf(assetsTestTemplate, v5extras), + sharedResourcesVersion: fmt.Sprintf(assetsTestTemplate, v5extras), + LogicVersion: fmt.Sprintf(assetsTestTemplate, v5extras), } -} -func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) { - for _, field := range assetHoldingFieldNames { - fs := assetHoldingFieldSpecByName[field] - if fs.version <= version && !strings.Contains(assetsTestProgram, field) { - t.Errorf("TestAssets missing field %v", field) + testAssetsByVersion := func(t *testing.T, assetsTestProgram string, version uint64) { + for _, field := range assetHoldingFieldNames { + fs := assetHoldingFieldSpecByName[field] + if fs.version <= version && !strings.Contains(assetsTestProgram, field) { + t.Errorf("TestAssets missing field %v", field) + } } - } - for _, field := range assetParamsFieldNames { - fs := assetParamsFieldSpecByName[field] - if fs.version <= version && !strings.Contains(assetsTestProgram, field) { - t.Errorf("TestAssets missing field %v", field) + for _, field := range assetParamsFieldNames { + fs := assetParamsFieldSpecByName[field] + if fs.version <= version && !strings.Contains(assetsTestProgram, field) { + t.Errorf("TestAssets missing field %v", field) + } } - } - - txn := makeSampleAppl(888) - pre := defaultAppParamsWithVersion(directRefEnabledVersion-1, txn) - require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) - now := defaultAppParamsWithVersion(version, txn) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - pre.Ledger = ledger - now.Ledger = ledger - - // bear in mind: the sample transaction has ForeignAccounts{55,77} - testApp(t, "int 5; int 55; asset_holding_get AssetBalance", now, "invalid Account reference 5") - // was legal to get balance on a non-ForeignAsset - testApp(t, "int 0; int 54; asset_holding_get AssetBalance; ==", pre) - // but not since directRefEnabledVersion - testApp(t, "int 0; int 54; asset_holding_get AssetBalance", now, "unavailable Asset 54") - - // it wasn't legal to use a direct ref for account - testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`, - directRefEnabledVersion-1, exp(1, "asset_holding_get AssetBalance arg 0 wanted type uint64...")) - // but it is now (empty asset yields 0,0 on stack) - testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, now) - // This is receiver, who is in Assets array - testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01"; int 55; asset_holding_get AssetBalance; ==`, now) - // But this is not in Assets, so illegal - testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02"; int 55; asset_holding_get AssetBalance; ==`, now, "invalid") - - // for params get, presence in ForeignAssets has always be required - testApp(t, "int 5; asset_params_get AssetTotal", pre, "Asset index 5 beyond") - testApp(t, "int 5; asset_params_get AssetTotal", now, "unavailable Asset 5") - params := basics.AssetParams{ - Total: 1000, - Decimals: 2, - DefaultFrozen: false, - UnitName: "ALGO", - AssetName: "", - URL: string(protocol.PaymentTx), - Manager: txn.Txn.Sender, - Reserve: txn.Txn.Receiver, - Freeze: txn.Txn.Receiver, - Clawback: txn.Txn.Receiver, - } + txn := makeSampleAppl(888) + pre := defaultAppParamsWithVersion(directRefEnabledVersion-1, txn) + require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) + + now := defaultAppParamsWithVersion(version, txn) + // Make an ep that has the transactions with all the basic stuff AND implied cross products + nowCross := defaultAppParamsWithVersion(version, txn) + convertEPToAccess(nowCross, true) + // Make an ep that ONLY puts the base types into tx.Access, no implied cross products + nowSimple := defaultAppParamsWithVersion(version, txn) + convertEPToAccess(nowSimple, false) + ledger := NewLedger( + map[basics.Address]uint64{ + txn.Txn.Sender: 1, + }, + ) + pre.Ledger = ledger + now.Ledger = ledger + nowCross.Ledger = ledger + + // bear in mind: the sample transaction has ForeignAccounts{55,77} + testApp(t, "int 5; int 55; asset_holding_get AssetBalance", now, "invalid Account reference 5") + // the foreign arrays are converted into tx.Access for nowCross. 5 is not an Address in tx.Access + if version >= sharedResourcesVersion { + testApp(t, "int 5; int 55; asset_holding_get AssetBalance", nowCross, "address reference 5 is not an Address") + testApp(t, "int 5; int 55; asset_holding_get AssetBalance", nowSimple, "address reference 5 is not an Address") + testApp(t, "int 50; int 55; asset_holding_get AssetBalance", nowCross, "invalid Account reference 5") // too big + testApp(t, "int 50; int 55; asset_holding_get AssetBalance", nowSimple, "invalid Account reference 5") // too big + } + // was legal to get asset balance on a non-ForeignAsset + testApp(t, "int 0; int 54; asset_holding_get AssetBalance; ==", pre) + // after directRefEnabledVersion, the asset must be included (whether using Foreign or Access) + testApp(t, "int 0; int 54; asset_holding_get AssetBalance", now, "unavailable Asset 54") + if version >= sharedResourcesVersion { + testApp(t, "int 0; int 54; asset_holding_get AssetBalance", nowCross, "unavailable Asset 54") + testApp(t, "int 0; int 54; asset_holding_get AssetBalance", nowSimple, "unavailable Asset 54") + } + // it wasn't legal to use a direct ref for account + testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`, + directRefEnabledVersion-1, exp(1, "asset_holding_get AssetBalance arg 0 wanted type uint64...")) + // but it is now (empty asset yields 0,0 on stack) + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, now) + if version >= sharedResourcesVersion { + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, nowCross) + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, nowSimple, + "unavailable Holding 55+MFXWK5LJMFXWK5LJMFXWK5LJMFXWK5LJMFXWK5LJMFXWK5LJGAYG54XDH4") + } + // This is receiver, who is in Accounts array + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01"; int 55; asset_holding_get AssetBalance; ==`, now) + if version >= sharedResourcesVersion { + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01"; int 55; asset_holding_get AssetBalance; ==`, nowCross) + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01"; int 55; asset_holding_get AssetBalance; ==`, nowSimple, + "unavailable Holding 55+MFXWK5LJMFXWK5LJMFXWK5LJMFXWK5LJMFXWK5LJMFXWK5LJGAY62VUCHY") + } + // But this address is not in Accounts, so illegal + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02"; int 55; asset_holding_get AssetBalance; ==`, now, "unavailable Account") + if version >= sharedResourcesVersion { + testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02"; int 55; asset_holding_get AssetBalance; ==`, nowCross, "unavailable Account") + } - ledger.NewAsset(txn.Txn.Sender, 55, params) - ledger.NewHolding(txn.Txn.Sender, 55, 123, true) - // For consistency you can now use an indirect ref in holding_get - // (recall ForeignAssets[0] = 55, which has balance 123) - testApp(t, "int 0; int 0; asset_holding_get AssetBalance; int 1; ==; assert; int 123; ==", now) - // but previous code would still try to read ASA 0 - testApp(t, "int 0; int 0; asset_holding_get AssetBalance; int 0; ==; assert; int 0; ==", pre) + // for params get, presence in ForeignAssets has always be required + testApp(t, "int 6; asset_params_get AssetTotal", pre, "6 is not a valid foreign asset slot") + testApp(t, "int 6; asset_params_get AssetTotal", now, "unavailable Asset 6") + if version >= sharedResourcesVersion { + testApp(t, "int 6; asset_params_get AssetTotal", nowCross, "unavailable Asset 6") + testApp(t, "int 6; asset_params_get AssetTotal", nowSimple, "unavailable Asset 6") + } - testApp(t, assetsTestProgram, now) + params := basics.AssetParams{ + Total: 1000, + Decimals: 2, + DefaultFrozen: false, + UnitName: "ALGO", + AssetName: "", + URL: string(protocol.PaymentTx), + Manager: txn.Txn.Sender, + Reserve: txn.Txn.Receiver, + Freeze: txn.Txn.Receiver, + Clawback: txn.Txn.Receiver, + } - // In current versions, can swap out the account index for the account - testApp(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now) - // Or an asset index for the asset id - testApp(t, strings.Replace(assetsTestProgram, "int 0//params", "int 55", -1), now) - // Or an index for the asset id - testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), now) + ledger.NewAsset(txn.Txn.Sender, 55, params) + ledger.NewHolding(txn.Txn.Sender, 55, 123, true) + // For consistency you can now use an indirect ref in holding_get + // (recall ForeignAssets[0] = 55, which has balance 123) + testApp(t, "int 0; int 0; asset_holding_get AssetBalance; int 1; ==; assert; int 123; ==", now) + if version >= sharedResourcesVersion { + // (recall Access[1] = 55, which has balance 123, tx.Access slots are always 1 based, so use 2. + testApp(t, "int 0; int 2; asset_holding_get AssetBalance; int 1; ==; assert; int 123; ==", nowCross) + } + // but previous code would still try to read ASA 0 + testApp(t, "int 0; int 0; asset_holding_get AssetBalance; int 0; ==; assert; int 0; ==", pre) + + testApp(t, assetsTestProgram, now) + // In current versions, can swap out the account index for the account + testApp(t, strings.ReplaceAll(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\""), now) + // Or an asset index for the asset id + testApp(t, strings.ReplaceAll(assetsTestProgram, "int 0//asset", "int 55"), now) + // Or an index for the asset id + testApp(t, strings.ReplaceAll(assetsTestProgram, "int 55", "int 0"), now) + + // same tests, but with tx.Access + if version >= sharedResourcesVersion { + // To run it with tx.Access, use slot #2 for the asset, and directly use the Receiver + assetsTestProgramA := substitute(assetsTestProgram, map[string]string{ + "int 0//asset": "int 2//asset", + "txna Accounts 1": "txn Receiver", + }) + testApp(t, assetsTestProgramA, nowCross) + testApp(t, strings.ReplaceAll(assetsTestProgramA, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\""), nowCross) + testApp(t, strings.ReplaceAll(assetsTestProgramA, "int 2//asset", "int 55"), nowCross) + testApp(t, strings.ReplaceAll(assetsTestProgramA, "int 55", "int 2"), nowCross) + } - // but old code cannot - testProg(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, exp(3, "asset_holding_get AssetBalance arg 0 wanted type uint64...")) + // but old code cannot + testProg(t, strings.ReplaceAll(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\""), directRefEnabledVersion-1, exp(4, "asset_holding_get AssetBalance arg 0 wanted type uint64...")) - if version < 5 { - // Can't run these with AppCreator anyway - testApp(t, strings.Replace(assetsTestProgram, "int 0//params", "int 55", -1), pre, "Asset index 55 beyond") - testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), pre, "err opcode") - } + if version < 5 { + // Can't run these with AppCreator anyway + testApp(t, strings.ReplaceAll(assetsTestProgram, "int 0//asset", "int 55"), pre, "55 is not a valid foreign asset slot") + testApp(t, strings.ReplaceAll(assetsTestProgram, "int 55", "int 0"), pre, "assert failed pc=53") // AssetBalance => 0,0 + } - // check holdings bool value - source := `intcblock 0 55 1 + // check asset_holdings bool value + source := `intcblock 0 55 1 intc_0 // 0, account idx (txn.Sender) intc_1 // 55 -asset_holding_get AssetFrozen -! -bnz error +asset_holding_get AssetFrozen; assert intc_0 // 0 == -bnz ok -error: -err -ok: -intc_2 // 1 ` - ledger.NewHolding(txn.Txn.Sender, 55, 123, false) - testApp(t, source, now) - - // check holdings invalid offsets - ops := testProg(t, source, version) - require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_holding_get"].Opcode, ops.Program[8]) - ops.Program[9] = 0x02 - _, err := EvalApp(ops.Program, 0, 888, now) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid asset_holding_get field 2") - - // check holdings bool value - source = `intcblock 0 1 + ledger.NewHolding(txn.Txn.Sender, 55, 123, false) + testApp(t, source, now) + if version >= sharedResourcesVersion { + testApp(t, source, nowCross) + } + // check asset_holding_get with invalid field number + ops := testProg(t, source, version) + require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_holding_get"].Opcode, ops.Program[8]) + ops.Program[9] = 0x02 + _, err := EvalApp(ops.Program, 0, 888, now) + require.ErrorContains(t, err, "invalid asset_holding_get field 2") + + // check asset_params bool value + source = `intcblock 0 1 intc_0 -asset_params_get AssetDefaultFrozen -! -bnz error +asset_params_get AssetDefaultFrozen; assert intc_1 == -bnz ok -error: -err -ok: -intc_1 ` - params.DefaultFrozen = true - ledger.NewAsset(txn.Txn.Sender, 55, params) - testApp(t, source, now) - // check holdings invalid offsets - ops = testProg(t, source, version) - require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_params_get"].Opcode, ops.Program[6]) - ops.Program[7] = 0x20 - _, err = EvalApp(ops.Program, 0, 888, now) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid asset_params_get field 32") - - // check empty string - source = `intcblock 0 1 -intc_0 // foreign asset idx (txn.ForeignAssets[0]) -asset_params_get AssetURL -! -bnz error -len -intc_0 -== -bnz ok -error: -err -ok: -intc_1 + params.DefaultFrozen = true + ledger.NewAsset(txn.Txn.Sender, 55, params) + testApp(t, source, now) + // check asset_params_get with invalid field number + ops = testProg(t, source, version) + require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_params_get"].Opcode, ops.Program[6]) + ops.Program[7] = 0x20 + _, err = EvalApp(ops.Program, 0, 888, now) + require.ErrorContains(t, err, "invalid asset_params_get field 32") + + // check empty string + source = ` +int 0 // foreign asset idx (txn.ForeignAssets[0]) +asset_params_get AssetURL; assert +len; ! ` - params.URL = "" - ledger.NewAsset(txn.Txn.Sender, 55, params) - testApp(t, source, now) + params.URL = "" + ledger.NewAsset(txn.Txn.Sender, 55, params) + testApp(t, source, now) - source = `intcblock 1 9 + source = `intcblock 1 9 intc_0 // foreign asset idx (txn.ForeignAssets[1]) -asset_params_get AssetURL -! -bnz error +asset_params_get AssetURL; assert len intc_1 == -bnz ok -error: -err -ok: -intc_0 ` - params.URL = "foobarbaz" - ledger.NewAsset(txn.Txn.Sender, 77, params) - testApp(t, source, now) + params.URL = "foobarbaz" + ledger.NewAsset(txn.Txn.Sender, 77, params) + testApp(t, source, now) - source = `intcblock 0 1 -intc_0 -asset_params_get AssetURL + source = ` +int 0 +asset_params_get AssetURL; assert ! -bnz error -intc_0 -== -bnz ok -error: -err -ok: -intc_1 ` - params.URL = "" - ledger.NewAsset(txn.Txn.Sender, 55, params) - testApp(t, notrack(source), now, "cannot compare ([]byte to uint64)") + params.URL = "" + ledger.NewAsset(txn.Txn.Sender, 55, params) + testApp(t, notrack(source), now, "! arg 0 wanted uint64") + } + + for v, source := range tests { + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + testAssetsByVersion(t, source, v) + }) + } } // TestAssetDisambiguation ensures we have a consistent interpretation of low @@ -1385,7 +1454,7 @@ func TestAssetDisambiguation(t *testing.T) { tx.ForeignAssets = []basics.AssetIndex{1, 256} if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // There's no direct use of assets IDs, so 1 is still the 1th slot (256) + // direct use of assets IDs is disallowed, so 1 is still the 1th slot (256) testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) } else { // Since 1 IS available, 1 means the assetid=1, not the 1th slot @@ -1405,7 +1474,7 @@ func TestAssetDisambiguation(t *testing.T) { testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) } - // but now if that resolution led to a number below 255, boom + // but now if that resolution led to a number below 256, boom tx.ForeignAssets = []basics.AssetIndex{256, 255} testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep, "low Asset lookup 255") @@ -1743,7 +1812,7 @@ intc_1 txn.Txn.Type = protocol.ApplicationCallTx txn.Txn.ApplicationID = 100 ep := defaultAppParams(txn) - err := CheckContract(ops.Program, ep) + err := CheckContract(ops.Program, 0, ep) require.NoError(t, err) ledger := NewLedger( @@ -2916,7 +2985,7 @@ func TestUnnamedResourceAccess(t *testing.T) { tc.policy.events = nil } } else { - testApp(t, source, ep, fmt.Sprintf("invalid Account reference %s", otherAccount)) + testApp(t, source, ep, fmt.Sprintf("unavailable Account %s", otherAccount)) } // Unaccessible app @@ -2988,12 +3057,7 @@ func TestUnnamedResourceAccess(t *testing.T) { tc.policy.events = nil } } else { - problem := "unavailable Account %s" - if ep.Proto.LogicSigVersion < 9 { - // Message is difference before sharedResourcesVersion - problem = "invalid Account reference %s" - } - testApp(t, source, ep, fmt.Sprintf(problem, otherAccount)) + testApp(t, source, ep, fmt.Sprintf("unavailable Account %s", otherAccount)) } // Unaccessible asset @@ -3051,12 +3115,7 @@ func TestUnnamedResourceAccess(t *testing.T) { tc.policy.events = nil } } else { - problem := "unavailable Account %s" - if ep.Proto.LogicSigVersion < 9 { - // Message is different before sharedResourcesVersion - problem = "invalid Account reference %s" - } - testApp(t, source, ep, fmt.Sprintf(problem, otherAccount)) + testApp(t, source, ep, fmt.Sprintf("unavailable Account %s", otherAccount)) } // Unaccessible box @@ -3972,15 +4031,21 @@ func TestTxnaLimits(t *testing.T) { t.Parallel() // txna came in v2, but Apps and Assets in v3. TestLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { - testApp(t, "txna Accounts "+strconv.Itoa(len(tx.Accounts))+";len", ep) - testApp(t, "txna Accounts "+strconv.Itoa(len(tx.Accounts)+1)+";len", ep, "invalid Accounts index") + if len(tx.Accounts) > 0 { // With tx.Access, sample txn has no Accounts + testApp(t, "txna Accounts "+strconv.Itoa(len(tx.Accounts))+";len", ep) + testApp(t, "txna Accounts "+strconv.Itoa(len(tx.Accounts)+1)+";len", ep, "invalid Accounts index") + } - testApp(t, "txna Applications "+strconv.Itoa(len(tx.ForeignApps)), ep) - testApp(t, "txna Applications "+strconv.Itoa(len(tx.ForeignApps)+1), ep, "invalid Applications index") + if len(tx.ForeignApps) > 0 { // With tx.Access, sample txn has no ForeignApps + testApp(t, "txna Applications "+strconv.Itoa(len(tx.ForeignApps)), ep) + testApp(t, "txna Applications "+strconv.Itoa(len(tx.ForeignApps)+1), ep, "invalid Applications index") + } // Assets and AppArgs have no implicit 0 index, so everything shifts - testApp(t, "txna Assets "+strconv.Itoa(len(tx.ForeignAssets)-1), ep) - testApp(t, "txna Assets "+strconv.Itoa(len(tx.ForeignAssets)), ep, "invalid Assets index") + if len(tx.ForeignApps) > 0 { // With tx.Access, sample txn has no ForeignApps + testApp(t, "txna Assets "+strconv.Itoa(len(tx.ForeignAssets)-1), ep) + testApp(t, "txna Assets "+strconv.Itoa(len(tx.ForeignAssets)), ep, "invalid Assets index") + } testApp(t, "txna ApplicationArgs "+strconv.Itoa(len(tx.ApplicationArgs)-1)+";len", ep) testApp(t, "txna ApplicationArgs "+strconv.Itoa(len(tx.ApplicationArgs))+";len", ep, "invalid ApplicationArgs index") diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 923279dd81..40ec36bca2 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -40,6 +40,7 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util" "pgregory.net/rapid" ) @@ -1088,7 +1089,7 @@ func TestTxnBadField(t *testing.T) { testLogicBytes(t, program, nil, "invalid txn field") // TODO: Check should know the type stack was wrong - // test txn does not accept ApplicationArgs and Accounts + // test that `txn` does not accept ApplicationArgs and Accounts, `txna` is necessary txnOpcode := OpsByName[LogicVersion]["txn"].Opcode txnaOpcode := OpsByName[LogicVersion]["txna"].Opcode @@ -1836,8 +1837,7 @@ func makeSampleTxn() transactions.SignedTxn { txn.Txn.AssetReceiver = txn.Txn.CloseRemainderTo txn.Txn.AssetCloseTo = txn.Txn.Sender txn.Txn.ApplicationID = basics.AppIndex(888) - txn.Txn.Accounts = make([]basics.Address, 1) - txn.Txn.Accounts[0] = txn.Txn.Receiver + txn.Txn.Accounts = []basics.Address{txn.Txn.Receiver} rekeyToAddr := []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui05") metadata := []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeuiHH") managerAddr := []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui06") @@ -2812,7 +2812,7 @@ int 100; byte 0x0201; == // types mismatch so this will fail err := testPanics(t, badsource, 1, "cannot compare") attrs := basics.Attributes(err) zeros := [256]int{} - scratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + scratch := util.Map(zeros[:], func(i int) any { return uint64(i) }) scratch[10] = uint64(5) scratch[15] = []byte{0x01, 0x02, 0x03, 0x00} require.Equal(t, map[string]any{ @@ -2831,7 +2831,7 @@ int 4; store 2 // store an int byte "jj"; store 3 // store a bytes int 1 ` - gscratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + gscratch := util.Map(zeros[:], func(i int) any { return uint64(i) }) gscratch[2] = uint64(4) gscratch[3] = []byte("jj") @@ -4506,7 +4506,7 @@ func TestAllowedOpcodesV2(t *testing.T) { require.Contains(t, source, spec.Name) ops := testProg(t, source, 2) // all opcodes allowed in stateful mode so use CheckStateful/EvalContract - err := CheckContract(ops.Program, aep) + err := CheckContract(ops.Program, 0, aep) require.NoError(t, err, source) _, err = EvalApp(ops.Program, 0, 0, aep) if spec.Name != "return" { diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 82801007e4..5d3c41fd9c 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -41,6 +41,7 @@ func (l *Ledger) DelBoxes(app basics.AppIndex, names ...string) { } } +var ConvertEPToAccess = convertEPToAccess var DefaultSigParams = defaultSigParams var DefaultAppParams = defaultAppParams var Exp = exp @@ -53,6 +54,7 @@ var NoTrack = notrack var TestLogic = testLogic var TestApp = testApp var TestAppBytes = testAppBytes +var TestAppFull = testAppFull var TestLogicRange = testLogicRange var TestProg = testProg var WithPanicOpcode = withPanicOpcode @@ -61,6 +63,7 @@ var WithPanicOpcode = withPanicOpcode // can't export call this "TestApps" because it looks like a Test function with // the wrong signature. But we can get that effect with the alias below. func TryApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ver uint64, ledger *Ledger, expected ...expect) (*EvalParams, error) { + t.Helper() return testApps(t, programs, txgroup, protoVer(ver), ledger, expected...) } diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index d093d97492..7eb3435746 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -53,7 +53,7 @@ type resources struct { // of the box - has it been modified in this txngroup? If yes, the size of // the box counts against the group writeBudget. So delete is NOT a dirtying // operation. - boxes map[BoxRef]bool + boxes map[basics.BoxRef]bool // dirtyBytes maintains a running count of the number of dirty bytes in `boxes` dirtyBytes uint64 @@ -74,6 +74,18 @@ func (r *resources) shareLocal(addr basics.Address, id basics.AppIndex) { r.sharedLocals[ledgercore.AccountApp{Address: addr, App: id}] = struct{}{} } +func (r *resources) shareBox(br basics.BoxRef, current basics.AppIndex) { + if br.App == 0 { + // "current app": Ignore if this is a create, else use ApplicationID + if current == 0 { + // When the create actually happens, and we learn the appID, we'll add it. + return + } + br.App = current + } + r.boxes[br] = false +} + // In the fill* and allows* routines, we pass the header and the fields in // separately, even though they are pointers into the same structure. That // prevents dumb attempts to use other fields from the transaction. @@ -223,24 +235,30 @@ func (cx *EvalContext) requireHolding(acct basics.Address, id basics.AssetIndex) return nil } if !cx.allowsHolding(acct, id) { - return fmt.Errorf("unavailable Holding %s x %d would be accessible", acct, id) + return fmt.Errorf("unavailable Holding %d+%s would be accessible", id, acct) } return nil } func (cx *EvalContext) requireLocals(acct basics.Address, id basics.AppIndex) error { if !cx.allowsLocals(acct, id) { - return fmt.Errorf("unavailable Local State %s x %d would be accessible", acct, id) + return fmt.Errorf("unavailable Local State %d+%s would be accessible", id, acct) } return nil } func (cx *EvalContext) allowsAssetTransfer(hdr *transactions.Header, tx *transactions.AssetTransferTxnFields) error { - err := cx.requireHolding(hdr.Sender, tx.XferAsset) - if err != nil { - return fmt.Errorf("axfer Sender: %w", err) + // After EnableInnerClawbackWithoutSenderHolding appears in a consensus + // update, we should remove it from consensus params and assume it's true in + // the next release. It only needs to be in there so that it gates the + // behavior change in the release it first appears. + if !cx.Proto.EnableInnerClawbackWithoutSenderHolding || tx.AssetSender.IsZero() { + err := cx.requireHolding(hdr.Sender, tx.XferAsset) + if err != nil { + return fmt.Errorf("axfer Sender: %w", err) + } } - err = cx.requireHolding(tx.AssetReceiver, tx.XferAsset) + err := cx.requireHolding(tx.AssetReceiver, tx.XferAsset) if err != nil { return fmt.Errorf("axfer AssetReceiver: %w", err) } @@ -271,6 +289,47 @@ func (cx *EvalContext) allowsAssetFreeze(hdr *transactions.Header, tx *transacti } func (r *resources) fillApplicationCall(ep *EvalParams, hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields) { + if tx.Access != nil { + r.fillApplicationCallAccess(ep, hdr, tx) + } else { + r.fillApplicationCallForeign(ep, hdr, tx) + } +} + +func (r *resources) fillApplicationCallAccess(ep *EvalParams, hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields) { + // The only implicitly available things are the sender, the app, and the sender's locals + r.sharedAccounts[hdr.Sender] = struct{}{} + if tx.ApplicationID != 0 { + r.sharedApps[tx.ApplicationID] = struct{}{} + r.shareLocal(hdr.Sender, tx.ApplicationID) + } + + // Access is a explicit list of resources that should be made "available" + for _, rr := range tx.Access { + switch { + case !rr.Address.IsZero(): + r.sharedAccounts[rr.Address] = struct{}{} + case rr.Asset != 0: + r.sharedAsas[rr.Asset] = struct{}{} + case rr.App != 0: + r.sharedApps[rr.App] = struct{}{} + case !rr.Holding.Empty(): + // ApplicationCallTxnFields.wellFormed ensures no error here. + address, asset, _ := rr.Holding.Resolve(tx.Access, hdr.Sender) + r.shareHolding(address, asset) + case !rr.Locals.Empty(): + // ApplicationCallTxnFields.wellFormed ensures no error here. + address, app, _ := rr.Locals.Resolve(tx.Access, hdr.Sender) + r.shareLocal(address, app) + case !rr.Box.Empty(): + // ApplicationCallTxnFields.wellFormed ensures no error here. + app, name, _ := rr.Box.Resolve(tx.Access) + r.shareBox(basics.BoxRef{App: app, Name: name}, tx.ApplicationID) + } + } +} + +func (r *resources) fillApplicationCallForeign(ep *EvalParams, hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields) { txAccounts := make([]basics.Address, 0, 2+len(tx.Accounts)+len(tx.ForeignApps)) txAccounts = append(txAccounts, hdr.Sender) txAccounts = append(txAccounts, tx.Accounts...) @@ -306,21 +365,14 @@ func (r *resources) fillApplicationCall(ep *EvalParams, hdr *transactions.Header } for _, br := range tx.Boxes { - var app basics.AppIndex - if br.Index == 0 { - // "current app": Ignore if this is a create, else use ApplicationID - if tx.ApplicationID == 0 { - // When the create actually happens, and we learn the appID, we'll add it. - continue - } - app = tx.ApplicationID - } else { - // Bounds check will already have been done by + app := basics.AppIndex(0) // 0 can be handled by shareBox as current + if br.Index != 0 { + // Upper bounds check will already have been done by // WellFormed. For testing purposes, it's better to panic // now than after returning a nil. - app = tx.ForeignApps[br.Index-1] // shift for the 0=this convention + app = tx.ForeignApps[br.Index-1] // shift for the 0=current convention } - r.boxes[BoxRef{app, string(br.Name)}] = false + r.shareBox(basics.BoxRef{App: app, Name: string(br.Name)}, tx.ApplicationID) } } diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index 8d0b2456f4..502f178f12 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -106,11 +106,11 @@ func TestAppSharing(t *testing.T) { // Now txn0 passes, but txn1 has an error because it can't see app 500 locals for appl1.Sender TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger, - Exp(1, "unavailable Local State "+appl1.Sender.String())) + Exp(1, "unavailable Local State 500+"+appl1.Sender.String())) // But it's ok in appl2, because appl2 uses the same Sender, even though the - // foreign-app is not repeated in appl2 because the holding being accessed - // is the one from tx0. + // localref is not repeated in appl2 because the locals being accessed is + // the one from tx0. TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 9, ledger) TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing Exp(1, "unavailable App 500")) @@ -120,7 +120,7 @@ func TestAppSharing(t *testing.T) { // as above, appl1 can't see the local state, but appl2 can b/c sender is same as appl0 TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger, - Exp(1, "unavailable Local State "+appl1.Sender.String())) + Exp(1, "unavailable Local State 900+"+appl1.Sender.String())) TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 9, ledger) TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 8, ledger, // v8=no sharing Exp(1, "unavailable App 900")) @@ -135,7 +135,7 @@ func TestAppSharing(t *testing.T) { appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0 // appl0.Sender is available, but 901's local state for it isn't (only 900 is, since 900 was called in tx0) TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, - Exp(1, "unavailable Local State "+appl0.Sender.String())) + Exp(1, "unavailable Local State 901+"+appl0.Sender.String())) // Add 901 to tx0's ForeignApps, and it works appl0.ForeignApps = append(appl0.ForeignApps, 901) // well, it will after we opt in TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, @@ -170,12 +170,12 @@ func TestAppSharing(t *testing.T) { ledger.NewLocals(pay1.Receiver, 900) // opt in sources = []string{`gtxn 1 Receiver; byte "key"; byte "val"; app_local_put; int 1`} TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, - Exp(0, "unavailable Local State "+pay1.Receiver.String())) + Exp(0, "unavailable Local State 900+"+pay1.Receiver.String())) // same for app_local_del sources = []string{`gtxn 1 Receiver; byte "key"; app_local_del; int 1`} TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, - Exp(0, "unavailable Local State "+pay1.Receiver.String())) + Exp(0, "unavailable Local State 900+"+pay1.Receiver.String())) // now, use an app call in tx1, with 900 in the foreign apps, so the local state is available appl1.ForeignApps = append(appl1.ForeignApps, 900) @@ -183,12 +183,207 @@ func TestAppSharing(t *testing.T) { sources = []string{`gtxn 1 Sender; byte "key"; byte "val"; app_local_put; int 1`} TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account - Exp(0, "invalid Account reference "+appl1.Sender.String())) + Exp(0, "unavailable Account "+appl1.Sender.String())) // same for app_local_del sources = []string{`gtxn 1 Sender; byte "key"; app_local_del; int 1`} TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account - Exp(0, "invalid Account reference "+appl1.Sender.String())) + Exp(0, "unavailable Account "+appl1.Sender.String())) +} + +// TestAppAccess confirms availablility of apps using tx.Access +func TestAppAccess(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Create some sample transactions. The main reason this a blackbox test + // (_test package) is to have access to txntest. + appl0 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 900, + Sender: basics.Address{1, 2, 3, 4}, + Access: []transactions.ResourceRef{{ + App: 500, + }, { + Locals: transactions.LocalsRef{ + Address: 0, + App: 1, + }, + }}, + } + + appl0noLocals := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 900, + Sender: basics.Address{1, 2, 3, 4}, + Access: []transactions.ResourceRef{{ + App: 500, + }}, + } + + appl1 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 901, + Sender: basics.Address{4, 3, 2, 1}, + } + + appl2 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 902, + Sender: basics.Address{1, 2, 3, 4}, + } + + pay1 := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: basics.Address{5, 5, 5, 5}, + Receiver: basics.Address{6, 6, 6, 6}, + } + + getSchema := "int 500; app_params_get AppGlobalNumByteSlice; !; assert; pop; int 1" + // before v9, no tx.Acccess + TestLogicRange(t, 5, 8, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + tx.Access = []transactions.ResourceRef{{Asset: 500}} + TestApp(t, getSchema, ep, + "pre-sharedResources program cannot be invoked with tx.Access", // fails at check time + "pre-sharedResources program cannot be invoked with tx.Access") + }) + // In v9, both can use 500, even though only the first has it in tx.Access + TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 9, nil) + + getLocalEx := `txn Sender; int 500; byte "some-key"; app_local_get_ex; pop; pop; int 1` + + // In contrast, when the second tx is reading the locals for a different + // account, no help + + // app_local_get* requires the address and the app exist, else the program fails + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, nil, + Exp(0, "no account")) + + _, _, ledger := MakeSampleEnv() + ledger.NewAccount(appl0.Sender, 100_000) + ledger.NewAccount(appl1.Sender, 100_000) + ledger.NewApp(appl0.Sender, 500, basics.AppParams{}) + ledger.NewLocals(appl0.Sender, 500) // opt in + // Now txn0 passes, but txn1 has an error because it can't see app 500 + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State")) + + // Locals won't be available if we only listed the app in tx.Access + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0noLocals, &appl1), 9, ledger, + Exp(0, "unavailable Local State")) + + // But it's ok in appl2, because appl2 uses the same Sender, even though the + // foreign-app is not repeated in appl2 because the holding being accessed + // is the one from tx0. + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 9, ledger) + + // Checking if an account is opted in has pretty much the same rules + optInCheck500 := "txn Sender; int 500; app_opted_in" + + // app_opted_in requires the address and the app exist, else the program fails + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account + Exp(0, "no account: "+appl0.Sender.String())) + + // Now txn0 passes, but txn1 has an error because it can't see app 500 locals for appl1.Sender + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State 500+"+appl1.Sender.String())) + + // But it's ok in appl2, because appl2 uses the same Sender, even though the + // foreign-app is not repeated in appl2 because the holding being accessed + // is the one from tx0. + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 9, ledger) + + // Confirm sharing applies to the app id called in tx0, not just access array + optInCheck900 := "txn Sender; int 900; app_opted_in; !" // we did not opt any senders into 900 + + // as above, appl1 can't see the local state, but appl2 can b/c sender is same as appl0 + TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State 900+"+appl1.Sender.String())) + TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 9, ledger) + + // Now, confirm that *setting* a local state in tx1 that was made available + // in tx0 works. The extra check here is that the change is recorded + // properly in EvalDelta. + putLocal := `txn ApplicationArgs 0; byte "X"; int 74; app_local_put; int 1` + + noop := `int 1` + sources := []string{noop, putLocal} + appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0 + // appl0.Sender is available, but 901's local state for it isn't (only 900's is, since 900 was called in tx0) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State 901+"+appl0.Sender.String())) + // Add 901 to tx0's Access. Still won't work because we don't include the Locals yet + appl0.Access = append(appl0.Access, transactions.ResourceRef{App: 901}) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State 901+"+appl0.Sender.String())) + // Now add the LocalsRef + appl0.Access = append(appl0.Access, transactions.ResourceRef{ + Locals: transactions.LocalsRef{ + Address: 0, + App: uint64(len(appl0.Access)), // reference 901 we added + }}) + // This error shows we have access, just not opted in. + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "account "+appl0.Sender.String()+" is not opted into 901")) + ledger.NewLocals(appl0.Sender, 901) // opt in + ep, _ := TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) + require.Len(t, ep.TxnGroup, 2) + ed := ep.TxnGroup[1].ApplyData.EvalDelta + require.Equal(t, map[uint64]basics.StateDelta{ + 1: { // no tx.Accounts, 1 indicates first in SharedAccts + "X": { + Action: basics.SetUintAction, + Uint: 74, + }, + }, + }, ed.LocalDeltas) + require.Len(t, ed.SharedAccts, 1) + require.Equal(t, ep.TxnGroup[0].Txn.Sender, ed.SharedAccts[0]) + + // when running all three, appl2 can't read the locals of app in tx0 and addr in tx1 + sources = []string{"", "", "gtxn 1 Sender; gtxn 0 Applications 0; byte 0xAA; app_local_get_ex"} + TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, + Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized + // same test of an account in Access of tx1 rather than Sender, but no Locals in tx1 + junk := "J5YDZLPOHWB5O6MVRHNFGY4JXIQAYYM6NUJWPBSYBBIXH5ENQ4Z5LTJELU" + j5y, err := basics.UnmarshalChecksumAddress(junk) + require.NoError(t, err) + appl1.Access = append(appl1.Access, transactions.ResourceRef{ + Address: j5y, + }) + sources = []string{"", "", ` +addr J5YDZLPOHWB5O6MVRHNFGY4JXIQAYYM6NUJWPBSYBBIXH5ENQ4Z5LTJELU +gtxn 0 Applications 0; byte 0xAA; app_local_get_ex`} + TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, + Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized + + // try to do a put on local state of the account in tx1, but tx0 ought not have access to that local state + ledger.NewAccount(pay1.Receiver, 200_000) + ledger.NewLocals(pay1.Receiver, 900) // opt in + sources = []string{`gtxn 1 Receiver; byte "key"; byte "val"; app_local_put; int 1`} + TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, + Exp(0, "unavailable Local State 900+"+pay1.Receiver.String())) + + // same for app_local_del + sources = []string{`gtxn 1 Receiver; byte "key"; app_local_del; int 1`} + TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, + Exp(0, "unavailable Local State 900+"+pay1.Receiver.String())) + + // now, use an app call in tx1, with 900 in Access + appl1.Access = append(appl1.Access, transactions.ResourceRef{App: 900}) + ledger.NewLocals(appl1.Sender, 900) // opt in + sources = []string{`gtxn 1 Sender; byte "key"; byte "val"; app_local_put; int 1`} + // not enough: app 900 is in Access, but not the locals + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(0, "unavailable Local State 900+"+appl1.Sender.String())) + appl1.Access = append(appl1.Access, transactions.ResourceRef{ + Locals: transactions.LocalsRef{ + App: uint64(len(appl1.Access)), + Address: 0, + }}) + // same for app_local_del + sources = []string{`gtxn 1 Sender; byte "key"; app_local_del; int 1`} + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) } // TestBetterLocalErrors confirms that we get specific errors about the missing @@ -422,9 +617,9 @@ func TestOtherTxSharing(t *testing.T) { TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger) TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger, - Exp(1, "invalid Account reference")) + Exp(1, "unavailable Account")) TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger, - Exp(0, "invalid Account reference")) + Exp(0, "unavailable Account")) } holdingAccess := ` @@ -441,7 +636,7 @@ func TestOtherTxSharing(t *testing.T) { withRef := appl withRef.ForeignAssets = []basics.AssetIndex{200} TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger, - Exp(1, "unavailable Holding "+senderAcct.String())) + Exp(1, "unavailable Holding 200+"+senderAcct.String())) }) t.Run("pay", func(t *testing.T) { // nolint:paralleltest // shares `ledger` // The receiver is available for algo balance reading @@ -451,7 +646,7 @@ func TestOtherTxSharing(t *testing.T) { // The other account is not (it's not even in the pay txn) appl.ApplicationArgs = [][]byte{otherAcct[:]} TestApps(t, []string{"", otherBalance}, txntest.Group(&pay, &appl), 9, ledger, - Exp(1, "invalid Account reference "+otherAcct.String())) + Exp(1, "unavailable Account "+otherAcct.String())) // The other account becomes accessible because used in CloseRemainderTo withClose := pay @@ -463,7 +658,7 @@ func TestOtherTxSharing(t *testing.T) { // The other account is not available even though it's all the extra addresses appl.ApplicationArgs = [][]byte{otherAcct[:]} TestApps(t, []string{"", otherBalance}, txntest.Group(&acfg, &appl), 9, ledger, - Exp(1, "invalid Account reference "+otherAcct.String())) + Exp(1, "unavailable Account "+otherAcct.String())) }) t.Run("axfer", func(t *testing.T) { // nolint:paralleltest // shares `ledger` @@ -490,7 +685,7 @@ func TestOtherTxSharing(t *testing.T) { // AssetCloseTo holding becomes available when set appl.ApplicationArgs = [][]byte{other2Acct[:], {byte(axfer.XferAsset)}} TestApps(t, []string{"", other2Balance}, txntest.Group(&axfer, &appl), 9, ledger, - Exp(1, "invalid Account reference "+other2Acct.String())) + Exp(1, "unavailable Account "+other2Acct.String())) TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger, Exp(1, "unavailable Account "+other2Acct.String())) @@ -511,7 +706,7 @@ func TestOtherTxSharing(t *testing.T) { appl.ApplicationArgs = [][]byte{senderAcct[:], {byte(afrz.FreezeAsset)}} TestApps(t, []string{"", senderBalance}, txntest.Group(&afrz, &appl), 9, ledger) TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger, - Exp(1, "unavailable Holding "+senderAcct.String())) + Exp(1, "unavailable Holding 200+"+senderAcct.String())) }) } @@ -691,7 +886,7 @@ int 1 ledger.NewHolding(payAcct, asa1, 1, false) appl.ApplicationArgs = [][]byte{payAcct[:], {asa1}} TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger, - Exp(2, "unavailable Holding "+payAcct.String())) + Exp(2, "unavailable Holding 201+"+payAcct.String())) }) t.Run("afrz", func(t *testing.T) { // nolint:paralleltest // shares `ledger` @@ -712,12 +907,12 @@ int 1 // can't axfer to the afrz sender because appAcct holding is not available from afrz appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}} TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - Exp(1, "unavailable Holding "+appAcct.String())) + Exp(1, "unavailable Holding 201+"+appAcct.String())) appl.ForeignAssets = []basics.AssetIndex{asa1} // _still_ can't axfer to sender because afrz sender's holding does NOT // become available (not note that complaint is now about that account) TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - Exp(1, "unavailable Holding "+senderAcct.String())) + Exp(1, "unavailable Holding 201+"+senderAcct.String())) // and not to the receiver which isn't in afrz appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}} @@ -738,7 +933,7 @@ int 1 appl.ForeignAssets = []basics.AssetIndex{asa2} // once added to appl's foreign array, the appl still lacks access to other's holding TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - Exp(1, "unavailable Holding "+otherAcct.String())) + Exp(1, "unavailable Holding 202+"+otherAcct.String())) // appl can acfg the asset from tx0 (which requires asset available, not holding) appl.ForeignAssets = []basics.AssetIndex{} @@ -778,10 +973,10 @@ int 1 appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}} TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, Exp(1, "unavailable Asset 202")) - // And adding asa2 does not fix this problem, because the other x 202 holding is unavailable + // And adding asa2 does not fix this problem, because the other 202 holding is unavailable appl.ForeignAssets = []basics.AssetIndex{asa2} TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, - Exp(1, "axfer AssetReceiver: unavailable Holding "+otherAcct.String()+" x 202")) + Exp(1, "axfer AssetReceiver: unavailable Holding 202+"+otherAcct.String())) // Now, conduct similar tests, but with the apps performing the // pays/axfers invoked from an outer app. Use various versions to check @@ -823,14 +1018,14 @@ int 1 // passed account's local state (which isn't available to the caller) innerCallWithAccount := fmt.Sprintf(innerCallTemplate, "addr "+otherAcct.String()+"; itxn_field Accounts") TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger, - Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) + Exp(1, "appl ApplicationID: unavailable Local State 88+"+otherAcct.String())) // the caller can't fix by passing 88 as a foreign app, because doing so // is not much different than the current situation: 88 is being called, // it's already available. innerCallWithBoth := fmt.Sprintf(innerCallTemplate, "addr "+otherAcct.String()+"; itxn_field Accounts; int 88; itxn_field Applications") TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, - Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) + Exp(1, "appl ApplicationID: unavailable Local State 88+"+otherAcct.String())) // the caller *can* do it if it originally had access to that 88 holding. appl0.ForeignApps = []basics.AppIndex{88} @@ -843,7 +1038,7 @@ int 1 appl.ApplicationArgs = [][]byte{{11}, otherAcct[:], {asa1}} appl0.ForeignApps = []basics.AppIndex{11} TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, - Exp(1, "appl ForeignApps: unavailable Local State "+otherAcct.String())) + Exp(1, "appl ForeignApps: unavailable Local State 88+"+otherAcct.String())) }) diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 9a800fd36d..edb16ea5c1 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -108,6 +108,16 @@ import ( // |-----> (*) MsgIsZero // |-----> HeartbeatTxnFieldsMaxSize() // +// HoldingRef +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> HoldingRefMaxSize() +// // KeyregTxnFields // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -118,6 +128,16 @@ import ( // |-----> (*) MsgIsZero // |-----> KeyregTxnFieldsMaxSize() // +// LocalsRef +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> LocalsRefMaxSize() +// // LogicSig // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -158,6 +178,16 @@ import ( // |-----> MsgIsZero // |-----> PaysetMaxSize() // +// ResourceRef +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> ResourceRefMaxSize() +// // SignedTxn // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -235,64 +265,80 @@ import ( func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0006Len := uint32(13) - var zb0006Mask uint16 /* 14 bits */ + zb0007Len := uint32(14) + var zb0007Mask uint16 /* 15 bits */ + if len((*z).Access) == 0 { + zb0007Len-- + zb0007Mask |= 0x2 + } if len((*z).ApplicationArgs) == 0 { - zb0006Len-- - zb0006Mask |= 0x2 + zb0007Len-- + zb0007Mask |= 0x4 } if (*z).OnCompletion == 0 { - zb0006Len-- - zb0006Mask |= 0x4 + zb0007Len-- + zb0007Mask |= 0x8 } if len((*z).ApprovalProgram) == 0 { - zb0006Len-- - zb0006Mask |= 0x8 + zb0007Len-- + zb0007Mask |= 0x10 } if len((*z).ForeignAssets) == 0 { - zb0006Len-- - zb0006Mask |= 0x10 + zb0007Len-- + zb0007Mask |= 0x20 } if len((*z).Accounts) == 0 { - zb0006Len-- - zb0006Mask |= 0x20 + zb0007Len-- + zb0007Mask |= 0x40 } if len((*z).Boxes) == 0 { - zb0006Len-- - zb0006Mask |= 0x40 + zb0007Len-- + zb0007Mask |= 0x80 } if (*z).ExtraProgramPages == 0 { - zb0006Len-- - zb0006Mask |= 0x80 + zb0007Len-- + zb0007Mask |= 0x100 } if len((*z).ForeignApps) == 0 { - zb0006Len-- - zb0006Mask |= 0x100 + zb0007Len-- + zb0007Mask |= 0x200 } if (*z).GlobalStateSchema.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x200 + zb0007Len-- + zb0007Mask |= 0x400 } if (*z).ApplicationID.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x400 + zb0007Len-- + zb0007Mask |= 0x800 } if (*z).LocalStateSchema.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x800 + zb0007Len-- + zb0007Mask |= 0x1000 } if (*z).RejectVersion == 0 { - zb0006Len-- - zb0006Mask |= 0x1000 + zb0007Len-- + zb0007Mask |= 0x2000 } if len((*z).ClearStateProgram) == 0 { - zb0006Len-- - zb0006Mask |= 0x2000 + zb0007Len-- + zb0007Mask |= 0x4000 } - // variable map header, size zb0006Len - o = append(o, 0x80|uint8(zb0006Len)) - if zb0006Len != 0 { - if (zb0006Mask & 0x2) == 0 { // if not empty + // variable map header, size zb0007Len + o = append(o, 0x80|uint8(zb0007Len)) + if zb0007Len != 0 { + if (zb0007Mask & 0x2) == 0 { // if not empty + // string "al" + o = append(o, 0xa2, 0x61, 0x6c) + if (*z).Access == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Access))) + } + for zb0005 := range (*z).Access { + o = (*z).Access[zb0005].MarshalMsg(o) + } + } + if (zb0007Mask & 0x4) == 0 { // if not empty // string "apaa" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x61) if (*z).ApplicationArgs == nil { @@ -304,17 +350,17 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendBytes(o, (*z).ApplicationArgs[zb0001]) } } - if (zb0006Mask & 0x4) == 0 { // if not empty + if (zb0007Mask & 0x8) == 0 { // if not empty // string "apan" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x6e) o = msgp.AppendUint64(o, uint64((*z).OnCompletion)) } - if (zb0006Mask & 0x8) == 0 { // if not empty + if (zb0007Mask & 0x10) == 0 { // if not empty // string "apap" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x70) o = msgp.AppendBytes(o, (*z).ApprovalProgram) } - if (zb0006Mask & 0x10) == 0 { // if not empty + if (zb0007Mask & 0x20) == 0 { // if not empty // string "apas" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x73) if (*z).ForeignAssets == nil { @@ -322,11 +368,11 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ForeignAssets))) } - for zb0005 := range (*z).ForeignAssets { - o = (*z).ForeignAssets[zb0005].MarshalMsg(o) + for zb0003 := range (*z).ForeignAssets { + o = (*z).ForeignAssets[zb0003].MarshalMsg(o) } } - if (zb0006Mask & 0x20) == 0 { // if not empty + if (zb0007Mask & 0x40) == 0 { // if not empty // string "apat" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x74) if (*z).Accounts == nil { @@ -338,7 +384,7 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = (*z).Accounts[zb0002].MarshalMsg(o) } } - if (zb0006Mask & 0x40) == 0 { // if not empty + if (zb0007Mask & 0x80) == 0 { // if not empty // string "apbx" o = append(o, 0xa4, 0x61, 0x70, 0x62, 0x78) if (*z).Boxes == nil { @@ -346,38 +392,38 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).Boxes))) } - for zb0004 := range (*z).Boxes { + for zb0006 := range (*z).Boxes { // omitempty: check for empty values - zb0007Len := uint32(2) - var zb0007Mask uint8 /* 3 bits */ - if (*z).Boxes[zb0004].Index == 0 { - zb0007Len-- - zb0007Mask |= 0x2 - } - if len((*z).Boxes[zb0004].Name) == 0 { - zb0007Len-- - zb0007Mask |= 0x4 - } - // variable map header, size zb0007Len - o = append(o, 0x80|uint8(zb0007Len)) - if (zb0007Mask & 0x2) == 0 { // if not empty + zb0008Len := uint32(2) + var zb0008Mask uint8 /* 3 bits */ + if (*z).Boxes[zb0006].Index == 0 { + zb0008Len-- + zb0008Mask |= 0x2 + } + if len((*z).Boxes[zb0006].Name) == 0 { + zb0008Len-- + zb0008Mask |= 0x4 + } + // variable map header, size zb0008Len + o = append(o, 0x80|uint8(zb0008Len)) + if (zb0008Mask & 0x2) == 0 { // if not empty // string "i" o = append(o, 0xa1, 0x69) - o = msgp.AppendUint64(o, (*z).Boxes[zb0004].Index) + o = msgp.AppendUint64(o, (*z).Boxes[zb0006].Index) } - if (zb0007Mask & 0x4) == 0 { // if not empty + if (zb0008Mask & 0x4) == 0 { // if not empty // string "n" o = append(o, 0xa1, 0x6e) - o = msgp.AppendBytes(o, (*z).Boxes[zb0004].Name) + o = msgp.AppendBytes(o, (*z).Boxes[zb0006].Name) } } } - if (zb0006Mask & 0x80) == 0 { // if not empty + if (zb0007Mask & 0x100) == 0 { // if not empty // string "apep" o = append(o, 0xa4, 0x61, 0x70, 0x65, 0x70) o = msgp.AppendUint32(o, (*z).ExtraProgramPages) } - if (zb0006Mask & 0x100) == 0 { // if not empty + if (zb0007Mask & 0x200) == 0 { // if not empty // string "apfa" o = append(o, 0xa4, 0x61, 0x70, 0x66, 0x61) if (*z).ForeignApps == nil { @@ -385,31 +431,31 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ForeignApps))) } - for zb0003 := range (*z).ForeignApps { - o = (*z).ForeignApps[zb0003].MarshalMsg(o) + for zb0004 := range (*z).ForeignApps { + o = (*z).ForeignApps[zb0004].MarshalMsg(o) } } - if (zb0006Mask & 0x200) == 0 { // if not empty + if (zb0007Mask & 0x400) == 0 { // if not empty // string "apgs" o = append(o, 0xa4, 0x61, 0x70, 0x67, 0x73) o = (*z).GlobalStateSchema.MarshalMsg(o) } - if (zb0006Mask & 0x400) == 0 { // if not empty + if (zb0007Mask & 0x800) == 0 { // if not empty // string "apid" o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) o = (*z).ApplicationID.MarshalMsg(o) } - if (zb0006Mask & 0x800) == 0 { // if not empty + if (zb0007Mask & 0x1000) == 0 { // if not empty // string "apls" o = append(o, 0xa4, 0x61, 0x70, 0x6c, 0x73) o = (*z).LocalStateSchema.MarshalMsg(o) } - if (zb0006Mask & 0x1000) == 0 { // if not empty + if (zb0007Mask & 0x2000) == 0 { // if not empty // string "aprv" o = append(o, 0xa4, 0x61, 0x70, 0x72, 0x76) o = msgp.AppendUint64(o, (*z).RejectVersion) } - if (zb0006Mask & 0x2000) == 0 { // if not empty + if (zb0007Mask & 0x4000) == 0 { // if not empty // string "apsu" o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75) o = msgp.AppendBytes(o, (*z).ClearStateProgram) @@ -432,55 +478,55 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm st.AllowableDepth-- var field []byte _ = field - var zb0006 int - var zb0007 bool - zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).ApplicationID.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationID") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- { - var zb0008 uint64 - zb0008, bts, err = msgp.ReadUint64Bytes(bts) + var zb0009 uint64 + zb0009, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OnCompletion") return } - (*z).OnCompletion = OnCompletion(zb0008) + (*z).OnCompletion = OnCompletion(zb0009) } } - if zb0006 > 0 { - zb0006-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0009 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0009), uint64(encodedMaxApplicationArgs)) + if zb0010 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0010), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0010 { + if zb0011 { (*z).ApplicationArgs = nil - } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0009 { - (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0009] + } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0010 { + (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0010] } else { - (*z).ApplicationArgs = make([][]byte, zb0009) + (*z).ApplicationArgs = make([][]byte, zb0010) } for zb0001 := range (*z).ApplicationArgs { (*z).ApplicationArgs[zb0001], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationArgs[zb0001]) @@ -490,26 +536,26 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm } } } - if zb0006 > 0 { - zb0006-- - var zb0011 int - var zb0012 bool - zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0011 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0011), uint64(encodedMaxAccounts)) + if zb0012 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0012), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0012 { + if zb0013 { (*z).Accounts = nil - } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0011 { - (*z).Accounts = ((*z).Accounts)[:zb0011] + } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0012 { + (*z).Accounts = ((*z).Accounts)[:zb0012] } else { - (*z).Accounts = make([]basics.Address, zb0011) + (*z).Accounts = make([]basics.Address, zb0012) } for zb0002 := range (*z).Accounts { bts, err = (*z).Accounts[zb0002].UnmarshalMsgWithState(bts, st) @@ -519,141 +565,199 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm } } } - if zb0006 > 0 { - zb0006-- - var zb0013 int - var zb0014 bool - zb0013, zb0014, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0014 int + var zb0015 bool + zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") + return + } + if zb0014 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedMaxForeignAssets)) + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") + return + } + if zb0015 { + (*z).ForeignAssets = nil + } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0014 { + (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0014] + } else { + (*z).ForeignAssets = make([]basics.AssetIndex, zb0014) + } + for zb0003 := range (*z).ForeignAssets { + bts, err = (*z).ForeignAssets[zb0003].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0003) + return + } + } + } + if zb0007 > 0 { + zb0007-- + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0013 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0013), uint64(encodedMaxForeignApps)) + if zb0016 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0014 { + if zb0017 { (*z).ForeignApps = nil - } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0013 { - (*z).ForeignApps = ((*z).ForeignApps)[:zb0013] + } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0016 { + (*z).ForeignApps = ((*z).ForeignApps)[:zb0016] } else { - (*z).ForeignApps = make([]basics.AppIndex, zb0013) + (*z).ForeignApps = make([]basics.AppIndex, zb0016) } - for zb0003 := range (*z).ForeignApps { - bts, err = (*z).ForeignApps[zb0003].UnmarshalMsgWithState(bts, st) + for zb0004 := range (*z).ForeignApps { + bts, err = (*z).ForeignApps[zb0004].UnmarshalMsgWithState(bts, st) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignApps", zb0003) + err = msgp.WrapError(err, "struct-from-array", "ForeignApps", zb0004) return } } } - if zb0006 > 0 { - zb0006-- - var zb0015 int - var zb0016 bool - zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Access") + return + } + if zb0018 > encodedMaxAccess { + err = msgp.ErrOverflow(uint64(zb0018), uint64(encodedMaxAccess)) + err = msgp.WrapError(err, "struct-from-array", "Access") + return + } + if zb0019 { + (*z).Access = nil + } else if (*z).Access != nil && cap((*z).Access) >= zb0018 { + (*z).Access = ((*z).Access)[:zb0018] + } else { + (*z).Access = make([]ResourceRef, zb0018) + } + for zb0005 := range (*z).Access { + bts, err = (*z).Access[zb0005].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Access", zb0005) + return + } + } + } + if zb0007 > 0 { + zb0007-- + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Boxes") return } - if zb0015 > encodedMaxBoxes { - err = msgp.ErrOverflow(uint64(zb0015), uint64(encodedMaxBoxes)) + if zb0020 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0020), uint64(encodedMaxBoxes)) err = msgp.WrapError(err, "struct-from-array", "Boxes") return } - if zb0016 { + if zb0021 { (*z).Boxes = nil - } else if (*z).Boxes != nil && cap((*z).Boxes) >= zb0015 { - (*z).Boxes = ((*z).Boxes)[:zb0015] + } else if (*z).Boxes != nil && cap((*z).Boxes) >= zb0020 { + (*z).Boxes = ((*z).Boxes)[:zb0020] } else { - (*z).Boxes = make([]BoxRef, zb0015) + (*z).Boxes = make([]BoxRef, zb0020) } - for zb0004 := range (*z).Boxes { - var zb0017 int - var zb0018 bool - zb0017, zb0018, bts, err = msgp.ReadMapHeaderBytes(bts) + for zb0006 := range (*z).Boxes { + var zb0022 int + var zb0023 bool + zb0022, zb0023, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0022, zb0023, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006) return } - if zb0017 > 0 { - zb0017-- - (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + if zb0022 > 0 { + zb0022-- + (*z).Boxes[zb0006].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array", "Index") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "struct-from-array", "Index") return } } - if zb0017 > 0 { - zb0017-- - var zb0019 int - zb0019, err = msgp.ReadBytesBytesHeader(bts) + if zb0022 > 0 { + zb0022-- + var zb0024 int + zb0024, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array", "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "struct-from-array", "Name") return } - if zb0019 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0019), uint64(bounds.MaxBytesKeyValueLen)) + if zb0024 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0024), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) + (*z).Boxes[zb0006].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0006].Name) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array", "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "struct-from-array", "Name") return } } - if zb0017 > 0 { - err = msgp.ErrTooManyArrayFields(zb0017) + if zb0022 > 0 { + err = msgp.ErrTooManyArrayFields(zb0022) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "struct-from-array") return } } } else { if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006) return } - if zb0018 { - (*z).Boxes[zb0004] = BoxRef{} + if zb0023 { + (*z).Boxes[zb0006] = BoxRef{} } - for zb0017 > 0 { - zb0017-- + for zb0022 > 0 { + zb0022-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006) return } switch string(field) { case "i": - (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + (*z).Boxes[zb0006].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "Index") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "Index") return } case "n": - var zb0020 int - zb0020, err = msgp.ReadBytesBytesHeader(bts) + var zb0025 int + zb0025, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "Name") return } - if zb0020 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0020), uint64(bounds.MaxBytesKeyValueLen)) + if zb0025 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0025), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) + (*z).Boxes[zb0006].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0006].Name) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006, "Name") return } default: err = msgp.ErrNoField(string(field)) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0006) return } } @@ -661,61 +765,32 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm } } } - if zb0006 > 0 { - zb0006-- - var zb0021 int - var zb0022 bool - zb0021, zb0022, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") - return - } - if zb0021 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0021), uint64(encodedMaxForeignAssets)) - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") - return - } - if zb0022 { - (*z).ForeignAssets = nil - } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0021 { - (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0021] - } else { - (*z).ForeignAssets = make([]basics.AssetIndex, zb0021) - } - for zb0005 := range (*z).ForeignAssets { - bts, err = (*z).ForeignAssets[zb0005].UnmarshalMsgWithState(bts, st) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0005) - return - } - } - } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).LocalStateSchema.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LocalStateSchema") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).GlobalStateSchema.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GlobalStateSchema") return } } - if zb0006 > 0 { - zb0006-- - var zb0023 int - zb0023, err = msgp.ReadBytesBytesHeader(bts) + if zb0007 > 0 { + zb0007-- + var zb0026 int + zb0026, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApprovalProgram") return } - if zb0023 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0023), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0026 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0026), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApprovalProgram) @@ -724,16 +799,16 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm return } } - if zb0006 > 0 { - zb0006-- - var zb0024 int - zb0024, err = msgp.ReadBytesBytesHeader(bts) + if zb0007 > 0 { + zb0007-- + var zb0027 int + zb0027, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ClearStateProgram") return } - if zb0024 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0024), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0027 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0027), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ClearStateProgram) @@ -742,24 +817,24 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).ExtraProgramPages, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExtraProgramPages") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).RejectVersion, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RejectVersion") return } } - if zb0006 > 0 { - err = msgp.ErrTooManyArrayFields(zb0006) + if zb0007 > 0 { + err = msgp.ErrTooManyArrayFields(zb0007) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -770,12 +845,12 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm err = msgp.WrapError(err) return } - if zb0007 { + if zb0008 { (*z) = ApplicationCallTxnFields{} } - for zb0006 > 0 { - zb0006-- - field, bts, err = msgp.ReadMapKeyZC(bts) + for zb0007 > 0 { + zb0007-- + field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) return @@ -789,33 +864,33 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm } case "apan": { - var zb0025 uint64 - zb0025, bts, err = msgp.ReadUint64Bytes(bts) + var zb0028 uint64 + zb0028, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OnCompletion") return } - (*z).OnCompletion = OnCompletion(zb0025) + (*z).OnCompletion = OnCompletion(zb0028) } case "apaa": - var zb0026 int - var zb0027 bool - zb0026, zb0027, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0029 int + var zb0030 bool + zb0029, zb0030, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0026 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0026), uint64(encodedMaxApplicationArgs)) + if zb0029 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0029), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0027 { + if zb0030 { (*z).ApplicationArgs = nil - } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0026 { - (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0026] + } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0029 { + (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0029] } else { - (*z).ApplicationArgs = make([][]byte, zb0026) + (*z).ApplicationArgs = make([][]byte, zb0029) } for zb0001 := range (*z).ApplicationArgs { (*z).ApplicationArgs[zb0001], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationArgs[zb0001]) @@ -825,24 +900,24 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm } } case "apat": - var zb0028 int - var zb0029 bool - zb0028, zb0029, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0031 int + var zb0032 bool + zb0031, zb0032, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Accounts") return } - if zb0028 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0028), uint64(encodedMaxAccounts)) + if zb0031 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0031), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "Accounts") return } - if zb0029 { + if zb0032 { (*z).Accounts = nil - } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0028 { - (*z).Accounts = ((*z).Accounts)[:zb0028] + } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0031 { + (*z).Accounts = ((*z).Accounts)[:zb0031] } else { - (*z).Accounts = make([]basics.Address, zb0028) + (*z).Accounts = make([]basics.Address, zb0031) } for zb0002 := range (*z).Accounts { bts, err = (*z).Accounts[zb0002].UnmarshalMsgWithState(bts, st) @@ -851,171 +926,198 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm return } } + case "apas": + var zb0033 int + var zb0034 bool + zb0033, zb0034, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "ForeignAssets") + return + } + if zb0033 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0033), uint64(encodedMaxForeignAssets)) + err = msgp.WrapError(err, "ForeignAssets") + return + } + if zb0034 { + (*z).ForeignAssets = nil + } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0033 { + (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0033] + } else { + (*z).ForeignAssets = make([]basics.AssetIndex, zb0033) + } + for zb0003 := range (*z).ForeignAssets { + bts, err = (*z).ForeignAssets[zb0003].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ForeignAssets", zb0003) + return + } + } case "apfa": - var zb0030 int - var zb0031 bool - zb0030, zb0031, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0035 int + var zb0036 bool + zb0035, zb0036, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ForeignApps") return } - if zb0030 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0030), uint64(encodedMaxForeignApps)) + if zb0035 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0035), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "ForeignApps") return } - if zb0031 { + if zb0036 { (*z).ForeignApps = nil - } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0030 { - (*z).ForeignApps = ((*z).ForeignApps)[:zb0030] + } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0035 { + (*z).ForeignApps = ((*z).ForeignApps)[:zb0035] + } else { + (*z).ForeignApps = make([]basics.AppIndex, zb0035) + } + for zb0004 := range (*z).ForeignApps { + bts, err = (*z).ForeignApps[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ForeignApps", zb0004) + return + } + } + case "al": + var zb0037 int + var zb0038 bool + zb0037, zb0038, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Access") + return + } + if zb0037 > encodedMaxAccess { + err = msgp.ErrOverflow(uint64(zb0037), uint64(encodedMaxAccess)) + err = msgp.WrapError(err, "Access") + return + } + if zb0038 { + (*z).Access = nil + } else if (*z).Access != nil && cap((*z).Access) >= zb0037 { + (*z).Access = ((*z).Access)[:zb0037] } else { - (*z).ForeignApps = make([]basics.AppIndex, zb0030) + (*z).Access = make([]ResourceRef, zb0037) } - for zb0003 := range (*z).ForeignApps { - bts, err = (*z).ForeignApps[zb0003].UnmarshalMsgWithState(bts, st) + for zb0005 := range (*z).Access { + bts, err = (*z).Access[zb0005].UnmarshalMsgWithState(bts, st) if err != nil { - err = msgp.WrapError(err, "ForeignApps", zb0003) + err = msgp.WrapError(err, "Access", zb0005) return } } case "apbx": - var zb0032 int - var zb0033 bool - zb0032, zb0033, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0039 int + var zb0040 bool + zb0039, zb0040, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Boxes") return } - if zb0032 > encodedMaxBoxes { - err = msgp.ErrOverflow(uint64(zb0032), uint64(encodedMaxBoxes)) + if zb0039 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0039), uint64(encodedMaxBoxes)) err = msgp.WrapError(err, "Boxes") return } - if zb0033 { + if zb0040 { (*z).Boxes = nil - } else if (*z).Boxes != nil && cap((*z).Boxes) >= zb0032 { - (*z).Boxes = ((*z).Boxes)[:zb0032] + } else if (*z).Boxes != nil && cap((*z).Boxes) >= zb0039 { + (*z).Boxes = ((*z).Boxes)[:zb0039] } else { - (*z).Boxes = make([]BoxRef, zb0032) + (*z).Boxes = make([]BoxRef, zb0039) } - for zb0004 := range (*z).Boxes { - var zb0034 int - var zb0035 bool - zb0034, zb0035, bts, err = msgp.ReadMapHeaderBytes(bts) + for zb0006 := range (*z).Boxes { + var zb0041 int + var zb0042 bool + zb0041, zb0042, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0034, zb0035, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0041, zb0042, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004) + err = msgp.WrapError(err, "Boxes", zb0006) return } - if zb0034 > 0 { - zb0034-- - (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + if zb0041 > 0 { + zb0041-- + (*z).Boxes[zb0006].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array", "Index") + err = msgp.WrapError(err, "Boxes", zb0006, "struct-from-array", "Index") return } } - if zb0034 > 0 { - zb0034-- - var zb0036 int - zb0036, err = msgp.ReadBytesBytesHeader(bts) + if zb0041 > 0 { + zb0041-- + var zb0043 int + zb0043, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array", "Name") + err = msgp.WrapError(err, "Boxes", zb0006, "struct-from-array", "Name") return } - if zb0036 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0036), uint64(bounds.MaxBytesKeyValueLen)) + if zb0043 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0043), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) + (*z).Boxes[zb0006].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0006].Name) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array", "Name") + err = msgp.WrapError(err, "Boxes", zb0006, "struct-from-array", "Name") return } } - if zb0034 > 0 { - err = msgp.ErrTooManyArrayFields(zb0034) + if zb0041 > 0 { + err = msgp.ErrTooManyArrayFields(zb0041) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array") + err = msgp.WrapError(err, "Boxes", zb0006, "struct-from-array") return } } } else { if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004) + err = msgp.WrapError(err, "Boxes", zb0006) return } - if zb0035 { - (*z).Boxes[zb0004] = BoxRef{} + if zb0042 { + (*z).Boxes[zb0006] = BoxRef{} } - for zb0034 > 0 { - zb0034-- + for zb0041 > 0 { + zb0041-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004) + err = msgp.WrapError(err, "Boxes", zb0006) return } switch string(field) { case "i": - (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + (*z).Boxes[zb0006].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "Index") + err = msgp.WrapError(err, "Boxes", zb0006, "Index") return } case "n": - var zb0037 int - zb0037, err = msgp.ReadBytesBytesHeader(bts) + var zb0044 int + zb0044, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "Name") + err = msgp.WrapError(err, "Boxes", zb0006, "Name") return } - if zb0037 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0037), uint64(bounds.MaxBytesKeyValueLen)) + if zb0044 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0044), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) + (*z).Boxes[zb0006].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0006].Name) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004, "Name") + err = msgp.WrapError(err, "Boxes", zb0006, "Name") return } default: err = msgp.ErrNoField(string(field)) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0004) + err = msgp.WrapError(err, "Boxes", zb0006) return } } } } } - case "apas": - var zb0038 int - var zb0039 bool - zb0038, zb0039, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "ForeignAssets") - return - } - if zb0038 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0038), uint64(encodedMaxForeignAssets)) - err = msgp.WrapError(err, "ForeignAssets") - return - } - if zb0039 { - (*z).ForeignAssets = nil - } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0038 { - (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0038] - } else { - (*z).ForeignAssets = make([]basics.AssetIndex, zb0038) - } - for zb0005 := range (*z).ForeignAssets { - bts, err = (*z).ForeignAssets[zb0005].UnmarshalMsgWithState(bts, st) - if err != nil { - err = msgp.WrapError(err, "ForeignAssets", zb0005) - return - } - } case "apls": bts, err = (*z).LocalStateSchema.UnmarshalMsgWithState(bts, st) if err != nil { @@ -1029,14 +1131,14 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm return } case "apap": - var zb0040 int - zb0040, err = msgp.ReadBytesBytesHeader(bts) + var zb0045 int + zb0045, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ApprovalProgram") return } - if zb0040 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0040), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0045 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0045), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApprovalProgram) @@ -1045,14 +1147,14 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm return } case "apsu": - var zb0041 int - zb0041, err = msgp.ReadBytesBytesHeader(bts) + var zb0046 int + zb0046, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ClearStateProgram") return } - if zb0041 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0041), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0046 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0046), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ClearStateProgram) @@ -1104,16 +1206,20 @@ func (z *ApplicationCallTxnFields) Msgsize() (s int) { s += (*z).Accounts[zb0002].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0003 := range (*z).ForeignApps { - s += (*z).ForeignApps[zb0003].Msgsize() + for zb0003 := range (*z).ForeignAssets { + s += (*z).ForeignAssets[zb0003].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0004 := range (*z).Boxes { - s += 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Boxes[zb0004].Name) + for zb0004 := range (*z).ForeignApps { + s += (*z).ForeignApps[zb0004].Msgsize() + } + s += 3 + msgp.ArrayHeaderSize + for zb0005 := range (*z).Access { + s += (*z).Access[zb0005].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0005 := range (*z).ForeignAssets { - s += (*z).ForeignAssets[zb0005].Msgsize() + for zb0006 := range (*z).Boxes { + s += 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Boxes[zb0006].Name) } s += 5 + (*z).LocalStateSchema.Msgsize() + 5 + (*z).GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ClearStateProgram) + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size return @@ -1121,7 +1227,7 @@ func (z *ApplicationCallTxnFields) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *ApplicationCallTxnFields) MsgIsZero() bool { - return ((*z).ApplicationID.MsgIsZero()) && ((*z).OnCompletion == 0) && (len((*z).ApplicationArgs) == 0) && (len((*z).Accounts) == 0) && (len((*z).ForeignApps) == 0) && (len((*z).Boxes) == 0) && (len((*z).ForeignAssets) == 0) && ((*z).LocalStateSchema.MsgIsZero()) && ((*z).GlobalStateSchema.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).ExtraProgramPages == 0) && ((*z).RejectVersion == 0) + return ((*z).ApplicationID.MsgIsZero()) && ((*z).OnCompletion == 0) && (len((*z).ApplicationArgs) == 0) && (len((*z).Accounts) == 0) && (len((*z).ForeignAssets) == 0) && (len((*z).ForeignApps) == 0) && (len((*z).Access) == 0) && (len((*z).Boxes) == 0) && ((*z).LocalStateSchema.MsgIsZero()) && ((*z).GlobalStateSchema.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).ExtraProgramPages == 0) && ((*z).RejectVersion == 0) } // MaxSize returns a maximum valid message size for this message type @@ -1132,14 +1238,17 @@ func ApplicationCallTxnFieldsMaxSize() (s int) { // Calculating size of slice: z.Accounts s += msgp.ArrayHeaderSize + ((encodedMaxAccounts) * (basics.AddressMaxSize())) s += 5 + // Calculating size of slice: z.ForeignAssets + s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize())) + s += 5 // Calculating size of slice: z.ForeignApps s += msgp.ArrayHeaderSize + ((encodedMaxForeignApps) * (basics.AppIndexMaxSize())) + s += 3 + // Calculating size of slice: z.Access + s += msgp.ArrayHeaderSize + ((encodedMaxAccess) * (ResourceRefMaxSize())) s += 5 // Calculating size of slice: z.Boxes s += msgp.ArrayHeaderSize + ((encodedMaxBoxes) * (BoxRefMaxSize())) - s += 5 - // Calculating size of slice: z.ForeignAssets - s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize())) s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + bounds.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + bounds.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size return } @@ -3153,6 +3262,149 @@ func HeartbeatTxnFieldsMaxSize() (s int) { return } +// MarshalMsg implements msgp.Marshaler +func (z *HoldingRef) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Address == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Asset == 0 { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "d" + o = append(o, 0xa1, 0x64) + o = msgp.AppendUint64(o, (*z).Address) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o = msgp.AppendUint64(o, (*z).Asset) + } + } + return +} + +func (_ *HoldingRef) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*HoldingRef) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *HoldingRef) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Address") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Asset, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Asset") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = HoldingRef{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "d": + (*z).Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Address") + return + } + case "s": + (*z).Asset, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Asset") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (z *HoldingRef) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *HoldingRef) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*HoldingRef) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *HoldingRef) Msgsize() (s int) { + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *HoldingRef) MsgIsZero() bool { + return ((*z).Address == 0) && ((*z).Asset == 0) +} + +// MaxSize returns a maximum valid message size for this message type +func HoldingRefMaxSize() (s int) { + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + return +} + // MarshalMsg implements msgp.Marshaler func (z *KeyregTxnFields) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -3412,56 +3664,199 @@ func KeyregTxnFieldsMaxSize() (s int) { } // MarshalMsg implements msgp.Marshaler -func (z *LogicSig) MarshalMsg(b []byte) (o []byte) { +func (z *LocalsRef) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0002Len := uint32(4) - var zb0002Mask uint8 /* 5 bits */ - if len((*z).Args) == 0 { - zb0002Len-- - zb0002Mask |= 0x2 - } - if len((*z).Logic) == 0 { - zb0002Len-- - zb0002Mask |= 0x4 - } - if (*z).Msig.MsgIsZero() { - zb0002Len-- - zb0002Mask |= 0x8 + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Address == 0 { + zb0001Len-- + zb0001Mask |= 0x2 } - if (*z).Sig.MsgIsZero() { - zb0002Len-- - zb0002Mask |= 0x10 + if (*z).App == 0 { + zb0001Len-- + zb0001Mask |= 0x4 } - // variable map header, size zb0002Len - o = append(o, 0x80|uint8(zb0002Len)) - if zb0002Len != 0 { - if (zb0002Mask & 0x2) == 0 { // if not empty - // string "arg" - o = append(o, 0xa3, 0x61, 0x72, 0x67) - if (*z).Args == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendArrayHeader(o, uint32(len((*z).Args))) - } - for zb0001 := range (*z).Args { - o = msgp.AppendBytes(o, (*z).Args[zb0001]) - } - } - if (zb0002Mask & 0x4) == 0 { // if not empty - // string "l" - o = append(o, 0xa1, 0x6c) - o = msgp.AppendBytes(o, (*z).Logic) - } - if (zb0002Mask & 0x8) == 0 { // if not empty - // string "msig" - o = append(o, 0xa4, 0x6d, 0x73, 0x69, 0x67) - o = (*z).Msig.MarshalMsg(o) + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "d" + o = append(o, 0xa1, 0x64) + o = msgp.AppendUint64(o, (*z).Address) } - if (zb0002Mask & 0x10) == 0 { // if not empty - // string "sig" - o = append(o, 0xa3, 0x73, 0x69, 0x67) - o = (*z).Sig.MarshalMsg(o) + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o = msgp.AppendUint64(o, (*z).App) + } + } + return +} + +func (_ *LocalsRef) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*LocalsRef) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *LocalsRef) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Address") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).App, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "App") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = LocalsRef{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "d": + (*z).Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Address") + return + } + case "p": + (*z).App, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "App") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (z *LocalsRef) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *LocalsRef) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*LocalsRef) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *LocalsRef) Msgsize() (s int) { + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *LocalsRef) MsgIsZero() bool { + return ((*z).Address == 0) && ((*z).App == 0) +} + +// MaxSize returns a maximum valid message size for this message type +func LocalsRefMaxSize() (s int) { + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *LogicSig) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0002Len := uint32(4) + var zb0002Mask uint8 /* 5 bits */ + if len((*z).Args) == 0 { + zb0002Len-- + zb0002Mask |= 0x2 + } + if len((*z).Logic) == 0 { + zb0002Len-- + zb0002Mask |= 0x4 + } + if (*z).Msig.MsgIsZero() { + zb0002Len-- + zb0002Mask |= 0x8 + } + if (*z).Sig.MsgIsZero() { + zb0002Len-- + zb0002Mask |= 0x10 + } + // variable map header, size zb0002Len + o = append(o, 0x80|uint8(zb0002Len)) + if zb0002Len != 0 { + if (zb0002Mask & 0x2) == 0 { // if not empty + // string "arg" + o = append(o, 0xa3, 0x61, 0x72, 0x67) + if (*z).Args == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Args))) + } + for zb0001 := range (*z).Args { + o = msgp.AppendBytes(o, (*z).Args[zb0001]) + } + } + if (zb0002Mask & 0x4) == 0 { // if not empty + // string "l" + o = append(o, 0xa1, 0x6c) + o = msgp.AppendBytes(o, (*z).Logic) + } + if (zb0002Mask & 0x8) == 0 { // if not empty + // string "msig" + o = append(o, 0xa4, 0x6d, 0x73, 0x69, 0x67) + o = (*z).Msig.MarshalMsg(o) + } + if (zb0002Mask & 0x10) == 0 { // if not empty + // string "sig" + o = append(o, 0xa3, 0x73, 0x69, 0x67) + o = (*z).Sig.MarshalMsg(o) } } return @@ -3895,116 +4290,841 @@ func (z *PaymentTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalSt return } -func (z *PaymentTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) { - return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) -} -func (_ *PaymentTxnFields) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*PaymentTxnFields) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *PaymentTxnFields) Msgsize() (s int) { - s = 1 + 4 + (*z).Receiver.Msgsize() + 4 + (*z).Amount.Msgsize() + 6 + (*z).CloseRemainderTo.Msgsize() - return -} - -// MsgIsZero returns whether this is a zero value -func (z *PaymentTxnFields) MsgIsZero() bool { - return ((*z).Receiver.MsgIsZero()) && ((*z).Amount.MsgIsZero()) && ((*z).CloseRemainderTo.MsgIsZero()) -} - -// MaxSize returns a maximum valid message size for this message type -func PaymentTxnFieldsMaxSize() (s int) { - s = 1 + 4 + basics.AddressMaxSize() + 4 + basics.MicroAlgosMaxSize() + 6 + basics.AddressMaxSize() - return -} - -// MarshalMsg implements msgp.Marshaler -func (z Payset) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - if z == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendArrayHeader(o, uint32(len(z))) - } - for za0001 := range z { - o = z[za0001].MarshalMsg(o) - } - return -} - -func (_ Payset) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(Payset) - if !ok { - _, ok = (z).(*Payset) - } - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *Payset) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { - if st.AllowableDepth == 0 { - err = msgp.ErrMaxDepthExceeded{} - return - } - st.AllowableDepth-- - var zb0002 int - var zb0003 bool - zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 > 100000 { - err = msgp.ErrOverflow(uint64(zb0002), uint64(100000)) - err = msgp.WrapError(err) - return - } - if zb0003 { - (*z) = nil - } else if (*z) != nil && cap((*z)) >= zb0002 { - (*z) = (*z)[:zb0002] - } else { - (*z) = make(Payset, zb0002) - } - for zb0001 := range *z { - bts, err = (*z)[zb0001].UnmarshalMsgWithState(bts, st) - if err != nil { - err = msgp.WrapError(err, zb0001) - return - } - } - o = bts - return -} - -func (z *Payset) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *PaymentTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *PaymentTxnFields) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*PaymentTxnFields) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *PaymentTxnFields) Msgsize() (s int) { + s = 1 + 4 + (*z).Receiver.Msgsize() + 4 + (*z).Amount.Msgsize() + 6 + (*z).CloseRemainderTo.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *PaymentTxnFields) MsgIsZero() bool { + return ((*z).Receiver.MsgIsZero()) && ((*z).Amount.MsgIsZero()) && ((*z).CloseRemainderTo.MsgIsZero()) +} + +// MaxSize returns a maximum valid message size for this message type +func PaymentTxnFieldsMaxSize() (s int) { + s = 1 + 4 + basics.AddressMaxSize() + 4 + basics.MicroAlgosMaxSize() + 6 + basics.AddressMaxSize() + return +} + +// MarshalMsg implements msgp.Marshaler +func (z Payset) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + if z == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len(z))) + } + for za0001 := range z { + o = z[za0001].MarshalMsg(o) + } + return +} + +func (_ Payset) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(Payset) + if !ok { + _, ok = (z).(*Payset) + } + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Payset) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var zb0002 int + var zb0003 bool + zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 > 100000 { + err = msgp.ErrOverflow(uint64(zb0002), uint64(100000)) + err = msgp.WrapError(err) + return + } + if zb0003 { + (*z) = nil + } else if (*z) != nil && cap((*z)) >= zb0002 { + (*z) = (*z)[:zb0002] + } else { + (*z) = make(Payset, zb0002) + } + for zb0001 := range *z { + bts, err = (*z)[zb0001].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + } + o = bts + return +} + +func (z *Payset) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *Payset) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Payset) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z Payset) Msgsize() (s int) { + s = msgp.ArrayHeaderSize + for za0001 := range z { + s += z[za0001].Msgsize() + } + return +} + +// MsgIsZero returns whether this is a zero value +func (z Payset) MsgIsZero() bool { + return len(z) == 0 +} + +// MaxSize returns a maximum valid message size for this message type +func PaysetMaxSize() (s int) { + // Calculating size of slice: z + s += msgp.ArrayHeaderSize + ((100000) * (SignedTxnInBlockMaxSize())) + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *ResourceRef) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(6) + var zb0001Mask uint8 /* 7 bits */ + if ((*z).Box.Index == 0) && (len((*z).Box.Name) == 0) { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Address.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if ((*z).Holding.Address == 0) && ((*z).Holding.Asset == 0) { + zb0001Len-- + zb0001Mask |= 0x8 + } + if ((*z).Locals.Address == 0) && ((*z).Locals.App == 0) { + zb0001Len-- + zb0001Mask |= 0x10 + } + if (*z).App.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x20 + } + if (*z).Asset.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x40 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "b" + o = append(o, 0xa1, 0x62) + // omitempty: check for empty values + zb0002Len := uint32(2) + var zb0002Mask uint8 /* 3 bits */ + if (*z).Box.Index == 0 { + zb0002Len-- + zb0002Mask |= 0x2 + } + if len((*z).Box.Name) == 0 { + zb0002Len-- + zb0002Mask |= 0x4 + } + // variable map header, size zb0002Len + o = append(o, 0x80|uint8(zb0002Len)) + if (zb0002Mask & 0x2) == 0 { // if not empty + // string "i" + o = append(o, 0xa1, 0x69) + o = msgp.AppendUint64(o, (*z).Box.Index) + } + if (zb0002Mask & 0x4) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = msgp.AppendBytes(o, (*z).Box.Name) + } + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "d" + o = append(o, 0xa1, 0x64) + o = (*z).Address.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "h" + o = append(o, 0xa1, 0x68) + // omitempty: check for empty values + zb0003Len := uint32(2) + var zb0003Mask uint8 /* 3 bits */ + if (*z).Holding.Address == 0 { + zb0003Len-- + zb0003Mask |= 0x2 + } + if (*z).Holding.Asset == 0 { + zb0003Len-- + zb0003Mask |= 0x4 + } + // variable map header, size zb0003Len + o = append(o, 0x80|uint8(zb0003Len)) + if (zb0003Mask & 0x2) == 0 { // if not empty + // string "d" + o = append(o, 0xa1, 0x64) + o = msgp.AppendUint64(o, (*z).Holding.Address) + } + if (zb0003Mask & 0x4) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o = msgp.AppendUint64(o, (*z).Holding.Asset) + } + } + if (zb0001Mask & 0x10) == 0 { // if not empty + // string "l" + o = append(o, 0xa1, 0x6c) + // omitempty: check for empty values + zb0004Len := uint32(2) + var zb0004Mask uint8 /* 3 bits */ + if (*z).Locals.Address == 0 { + zb0004Len-- + zb0004Mask |= 0x2 + } + if (*z).Locals.App == 0 { + zb0004Len-- + zb0004Mask |= 0x4 + } + // variable map header, size zb0004Len + o = append(o, 0x80|uint8(zb0004Len)) + if (zb0004Mask & 0x2) == 0 { // if not empty + // string "d" + o = append(o, 0xa1, 0x64) + o = msgp.AppendUint64(o, (*z).Locals.Address) + } + if (zb0004Mask & 0x4) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o = msgp.AppendUint64(o, (*z).Locals.App) + } + } + if (zb0001Mask & 0x20) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o = (*z).App.MarshalMsg(o) + } + if (zb0001Mask & 0x40) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o = (*z).Asset.MarshalMsg(o) + } + } + return +} + +func (_ *ResourceRef) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*ResourceRef) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *ResourceRef) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Address.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Address") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Asset.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Asset") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).App.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "App") + return + } + } + if zb0001 > 0 { + zb0001-- + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding") + return + } + if zb0003 > 0 { + zb0003-- + (*z).Holding.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding", "struct-from-array", "Address") + return + } + } + if zb0003 > 0 { + zb0003-- + (*z).Holding.Asset, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding", "struct-from-array", "Asset") + return + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding") + return + } + if zb0004 { + (*z).Holding = HoldingRef{} + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding") + return + } + switch string(field) { + case "d": + (*z).Holding.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding", "Address") + return + } + case "s": + (*z).Holding.Asset, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding", "Asset") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Holding") + return + } + } + } + } + } + if zb0001 > 0 { + zb0001-- + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals") + return + } + if zb0005 > 0 { + zb0005-- + (*z).Locals.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals", "struct-from-array", "Address") + return + } + } + if zb0005 > 0 { + zb0005-- + (*z).Locals.App, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals", "struct-from-array", "App") + return + } + } + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals") + return + } + if zb0006 { + (*z).Locals = LocalsRef{} + } + for zb0005 > 0 { + zb0005-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals") + return + } + switch string(field) { + case "d": + (*z).Locals.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals", "Address") + return + } + case "p": + (*z).Locals.App, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals", "App") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Locals") + return + } + } + } + } + } + if zb0001 > 0 { + zb0001-- + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box") + return + } + if zb0007 > 0 { + zb0007-- + (*z).Box.Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "struct-from-array", "Index") + return + } + } + if zb0007 > 0 { + zb0007-- + var zb0009 int + zb0009, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "struct-from-array", "Name") + return + } + if zb0009 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0009), uint64(bounds.MaxBytesKeyValueLen)) + return + } + (*z).Box.Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Box.Name) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "struct-from-array", "Name") + return + } + } + if zb0007 > 0 { + err = msgp.ErrTooManyArrayFields(zb0007) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box") + return + } + if zb0008 { + (*z).Box = BoxRef{} + } + for zb0007 > 0 { + zb0007-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box") + return + } + switch string(field) { + case "i": + (*z).Box.Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "Index") + return + } + case "n": + var zb0010 int + zb0010, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "Name") + return + } + if zb0010 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0010), uint64(bounds.MaxBytesKeyValueLen)) + return + } + (*z).Box.Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Box.Name) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box", "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Box") + return + } + } + } + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = ResourceRef{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "d": + bts, err = (*z).Address.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Address") + return + } + case "s": + bts, err = (*z).Asset.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Asset") + return + } + case "p": + bts, err = (*z).App.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "App") + return + } + case "h": + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Holding") + return + } + if zb0011 > 0 { + zb0011-- + (*z).Holding.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Holding", "struct-from-array", "Address") + return + } + } + if zb0011 > 0 { + zb0011-- + (*z).Holding.Asset, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Holding", "struct-from-array", "Asset") + return + } + } + if zb0011 > 0 { + err = msgp.ErrTooManyArrayFields(zb0011) + if err != nil { + err = msgp.WrapError(err, "Holding", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "Holding") + return + } + if zb0012 { + (*z).Holding = HoldingRef{} + } + for zb0011 > 0 { + zb0011-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "Holding") + return + } + switch string(field) { + case "d": + (*z).Holding.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Holding", "Address") + return + } + case "s": + (*z).Holding.Asset, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Holding", "Asset") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "Holding") + return + } + } + } + } + case "l": + var zb0013 int + var zb0014 bool + zb0013, zb0014, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0013, zb0014, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Locals") + return + } + if zb0013 > 0 { + zb0013-- + (*z).Locals.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Locals", "struct-from-array", "Address") + return + } + } + if zb0013 > 0 { + zb0013-- + (*z).Locals.App, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Locals", "struct-from-array", "App") + return + } + } + if zb0013 > 0 { + err = msgp.ErrTooManyArrayFields(zb0013) + if err != nil { + err = msgp.WrapError(err, "Locals", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "Locals") + return + } + if zb0014 { + (*z).Locals = LocalsRef{} + } + for zb0013 > 0 { + zb0013-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "Locals") + return + } + switch string(field) { + case "d": + (*z).Locals.Address, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Locals", "Address") + return + } + case "p": + (*z).Locals.App, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Locals", "App") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "Locals") + return + } + } + } + } + case "b": + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Box") + return + } + if zb0015 > 0 { + zb0015-- + (*z).Box.Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Box", "struct-from-array", "Index") + return + } + } + if zb0015 > 0 { + zb0015-- + var zb0017 int + zb0017, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "Box", "struct-from-array", "Name") + return + } + if zb0017 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0017), uint64(bounds.MaxBytesKeyValueLen)) + return + } + (*z).Box.Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Box.Name) + if err != nil { + err = msgp.WrapError(err, "Box", "struct-from-array", "Name") + return + } + } + if zb0015 > 0 { + err = msgp.ErrTooManyArrayFields(zb0015) + if err != nil { + err = msgp.WrapError(err, "Box", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "Box") + return + } + if zb0016 { + (*z).Box = BoxRef{} + } + for zb0015 > 0 { + zb0015-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "Box") + return + } + switch string(field) { + case "i": + (*z).Box.Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Box", "Index") + return + } + case "n": + var zb0018 int + zb0018, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "Box", "Name") + return + } + if zb0018 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0018), uint64(bounds.MaxBytesKeyValueLen)) + return + } + (*z).Box.Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Box.Name) + if err != nil { + err = msgp.WrapError(err, "Box", "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "Box") + return + } + } + } + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (z *ResourceRef) UnmarshalMsg(bts []byte) (o []byte, err error) { return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) } -func (_ *Payset) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*Payset) +func (_ *ResourceRef) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*ResourceRef) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z Payset) Msgsize() (s int) { - s = msgp.ArrayHeaderSize - for za0001 := range z { - s += z[za0001].Msgsize() - } +func (z *ResourceRef) Msgsize() (s int) { + s = 1 + 2 + (*z).Address.Msgsize() + 2 + (*z).Asset.Msgsize() + 2 + (*z).App.Msgsize() + 2 + 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Box.Name) return } // MsgIsZero returns whether this is a zero value -func (z Payset) MsgIsZero() bool { - return len(z) == 0 +func (z *ResourceRef) MsgIsZero() bool { + return ((*z).Address.MsgIsZero()) && ((*z).Asset.MsgIsZero()) && ((*z).App.MsgIsZero()) && (((*z).Holding.Address == 0) && ((*z).Holding.Asset == 0)) && (((*z).Locals.Address == 0) && ((*z).Locals.App == 0)) && (((*z).Box.Index == 0) && (len((*z).Box.Name) == 0)) } // MaxSize returns a maximum valid message size for this message type -func PaysetMaxSize() (s int) { - // Calculating size of slice: z - s += msgp.ArrayHeaderSize + ((100000) * (SignedTxnInBlockMaxSize())) +func ResourceRefMaxSize() (s int) { + s = 1 + 2 + basics.AddressMaxSize() + 2 + basics.AssetIndexMaxSize() + 2 + basics.AppIndexMaxSize() + 2 + 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + 1 + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + bounds.MaxBytesKeyValueLen return } @@ -5228,224 +6348,240 @@ func StateProofTxnFieldsMaxSize() (s int) { func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0007Len := uint32(48) - var zb0007Mask uint64 /* 57 bits */ + zb0008Len := uint32(49) + var zb0008Mask uint64 /* 58 bits */ if (*z).AssetTransferTxnFields.AssetAmount == 0 { - zb0007Len-- - zb0007Mask |= 0x200 + zb0008Len-- + zb0008Mask |= 0x200 } if (*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x400 + zb0008Len-- + zb0008Mask |= 0x400 } if (*z).AssetFreezeTxnFields.AssetFrozen == false { - zb0007Len-- - zb0007Mask |= 0x800 + zb0008Len-- + zb0008Mask |= 0x800 + } + if len((*z).ApplicationCallTxnFields.Access) == 0 { + zb0008Len-- + zb0008Mask |= 0x1000 } if (*z).PaymentTxnFields.Amount.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x1000 + zb0008Len-- + zb0008Mask |= 0x2000 } if len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0 { - zb0007Len-- - zb0007Mask |= 0x2000 + zb0008Len-- + zb0008Mask |= 0x4000 } if (*z).ApplicationCallTxnFields.OnCompletion == 0 { - zb0007Len-- - zb0007Mask |= 0x4000 + zb0008Len-- + zb0008Mask |= 0x8000 } if len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0 { - zb0007Len-- - zb0007Mask |= 0x8000 + zb0008Len-- + zb0008Mask |= 0x10000 } if (*z).AssetConfigTxnFields.AssetParams.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x10000 + zb0008Len-- + zb0008Mask |= 0x20000 } if len((*z).ApplicationCallTxnFields.ForeignAssets) == 0 { - zb0007Len-- - zb0007Mask |= 0x20000 + zb0008Len-- + zb0008Mask |= 0x40000 } if len((*z).ApplicationCallTxnFields.Accounts) == 0 { - zb0007Len-- - zb0007Mask |= 0x40000 + zb0008Len-- + zb0008Mask |= 0x80000 } if len((*z).ApplicationCallTxnFields.Boxes) == 0 { - zb0007Len-- - zb0007Mask |= 0x80000 + zb0008Len-- + zb0008Mask |= 0x100000 } if (*z).ApplicationCallTxnFields.ExtraProgramPages == 0 { - zb0007Len-- - zb0007Mask |= 0x100000 + zb0008Len-- + zb0008Mask |= 0x200000 } if len((*z).ApplicationCallTxnFields.ForeignApps) == 0 { - zb0007Len-- - zb0007Mask |= 0x200000 + zb0008Len-- + zb0008Mask |= 0x400000 } if (*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x400000 + zb0008Len-- + zb0008Mask |= 0x800000 } if (*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x800000 + zb0008Len-- + zb0008Mask |= 0x1000000 } if (*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x1000000 + zb0008Len-- + zb0008Mask |= 0x2000000 } if (*z).ApplicationCallTxnFields.RejectVersion == 0 { - zb0007Len-- - zb0007Mask |= 0x2000000 + zb0008Len-- + zb0008Mask |= 0x4000000 } if len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0 { - zb0007Len-- - zb0007Mask |= 0x4000000 + zb0008Len-- + zb0008Mask |= 0x8000000 } if (*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x8000000 + zb0008Len-- + zb0008Mask |= 0x10000000 } if (*z).AssetTransferTxnFields.AssetSender.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x10000000 + zb0008Len-- + zb0008Mask |= 0x20000000 } if (*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x20000000 + zb0008Len-- + zb0008Mask |= 0x40000000 } if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x40000000 + zb0008Len-- + zb0008Mask |= 0x80000000 } if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x80000000 + zb0008Len-- + zb0008Mask |= 0x100000000 } if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x100000000 + zb0008Len-- + zb0008Mask |= 0x200000000 } if (*z).Header.Fee.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x200000000 + zb0008Len-- + zb0008Mask |= 0x400000000 } if (*z).Header.FirstValid.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x400000000 + zb0008Len-- + zb0008Mask |= 0x800000000 } if (*z).Header.GenesisID == "" { - zb0007Len-- - zb0007Mask |= 0x800000000 + zb0008Len-- + zb0008Mask |= 0x1000000000 } if (*z).Header.GenesisHash.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x1000000000 + zb0008Len-- + zb0008Mask |= 0x2000000000 } if (*z).Header.Group.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x2000000000 + zb0008Len-- + zb0008Mask |= 0x4000000000 } if (*z).HeartbeatTxnFields == nil { - zb0007Len-- - zb0007Mask |= 0x4000000000 + zb0008Len-- + zb0008Mask |= 0x8000000000 } if (*z).Header.LastValid.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x8000000000 + zb0008Len-- + zb0008Mask |= 0x10000000000 } if (*z).Header.Lease == ([32]byte{}) { - zb0007Len-- - zb0007Mask |= 0x10000000000 + zb0008Len-- + zb0008Mask |= 0x20000000000 } if (*z).KeyregTxnFields.Nonparticipation == false { - zb0007Len-- - zb0007Mask |= 0x20000000000 + zb0008Len-- + zb0008Mask |= 0x40000000000 } if len((*z).Header.Note) == 0 { - zb0007Len-- - zb0007Mask |= 0x40000000000 + zb0008Len-- + zb0008Mask |= 0x80000000000 } if (*z).PaymentTxnFields.Receiver.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x80000000000 + zb0008Len-- + zb0008Mask |= 0x100000000000 } if (*z).Header.RekeyTo.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x100000000000 + zb0008Len-- + zb0008Mask |= 0x200000000000 } if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x200000000000 + zb0008Len-- + zb0008Mask |= 0x400000000000 } if (*z).Header.Sender.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x400000000000 + zb0008Len-- + zb0008Mask |= 0x800000000000 } if (*z).StateProofTxnFields.StateProof.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x800000000000 + zb0008Len-- + zb0008Mask |= 0x1000000000000 } if (*z).StateProofTxnFields.Message.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x1000000000000 + zb0008Len-- + zb0008Mask |= 0x2000000000000 } if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x2000000000000 + zb0008Len-- + zb0008Mask |= 0x4000000000000 } if (*z).StateProofTxnFields.StateProofType.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x4000000000000 + zb0008Len-- + zb0008Mask |= 0x8000000000000 } if (*z).Type.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x8000000000000 + zb0008Len-- + zb0008Mask |= 0x10000000000000 } if (*z).KeyregTxnFields.VoteFirst.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x10000000000000 + zb0008Len-- + zb0008Mask |= 0x20000000000000 } if (*z).KeyregTxnFields.VoteKeyDilution == 0 { - zb0007Len-- - zb0007Mask |= 0x20000000000000 + zb0008Len-- + zb0008Mask |= 0x40000000000000 } if (*z).KeyregTxnFields.VotePK.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x40000000000000 + zb0008Len-- + zb0008Mask |= 0x80000000000000 } if (*z).KeyregTxnFields.VoteLast.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x80000000000000 + zb0008Len-- + zb0008Mask |= 0x100000000000000 } if (*z).AssetTransferTxnFields.XferAsset.MsgIsZero() { - zb0007Len-- - zb0007Mask |= 0x100000000000000 + zb0008Len-- + zb0008Mask |= 0x200000000000000 } - // variable map header, size zb0007Len - o = msgp.AppendMapHeader(o, zb0007Len) - if zb0007Len != 0 { - if (zb0007Mask & 0x200) == 0 { // if not empty + // variable map header, size zb0008Len + o = msgp.AppendMapHeader(o, zb0008Len) + if zb0008Len != 0 { + if (zb0008Mask & 0x200) == 0 { // if not empty // string "aamt" o = append(o, 0xa4, 0x61, 0x61, 0x6d, 0x74) o = msgp.AppendUint64(o, (*z).AssetTransferTxnFields.AssetAmount) } - if (zb0007Mask & 0x400) == 0 { // if not empty + if (zb0008Mask & 0x400) == 0 { // if not empty // string "aclose" o = append(o, 0xa6, 0x61, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).AssetTransferTxnFields.AssetCloseTo.MarshalMsg(o) } - if (zb0007Mask & 0x800) == 0 { // if not empty + if (zb0008Mask & 0x800) == 0 { // if not empty // string "afrz" o = append(o, 0xa4, 0x61, 0x66, 0x72, 0x7a) o = msgp.AppendBool(o, (*z).AssetFreezeTxnFields.AssetFrozen) } - if (zb0007Mask & 0x1000) == 0 { // if not empty + if (zb0008Mask & 0x1000) == 0 { // if not empty + // string "al" + o = append(o, 0xa2, 0x61, 0x6c) + if (*z).ApplicationCallTxnFields.Access == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).ApplicationCallTxnFields.Access))) + } + for zb0006 := range (*z).ApplicationCallTxnFields.Access { + o = (*z).ApplicationCallTxnFields.Access[zb0006].MarshalMsg(o) + } + } + if (zb0008Mask & 0x2000) == 0 { // if not empty // string "amt" o = append(o, 0xa3, 0x61, 0x6d, 0x74) o = (*z).PaymentTxnFields.Amount.MarshalMsg(o) } - if (zb0007Mask & 0x2000) == 0 { // if not empty + if (zb0008Mask & 0x4000) == 0 { // if not empty // string "apaa" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x61) if (*z).ApplicationCallTxnFields.ApplicationArgs == nil { @@ -5457,22 +6593,22 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) } } - if (zb0007Mask & 0x4000) == 0 { // if not empty + if (zb0008Mask & 0x8000) == 0 { // if not empty // string "apan" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x6e) o = msgp.AppendUint64(o, uint64((*z).ApplicationCallTxnFields.OnCompletion)) } - if (zb0007Mask & 0x8000) == 0 { // if not empty + if (zb0008Mask & 0x10000) == 0 { // if not empty // string "apap" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x70) o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ApprovalProgram) } - if (zb0007Mask & 0x10000) == 0 { // if not empty + if (zb0008Mask & 0x20000) == 0 { // if not empty // string "apar" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x72) o = (*z).AssetConfigTxnFields.AssetParams.MarshalMsg(o) } - if (zb0007Mask & 0x20000) == 0 { // if not empty + if (zb0008Mask & 0x40000) == 0 { // if not empty // string "apas" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x73) if (*z).ApplicationCallTxnFields.ForeignAssets == nil { @@ -5480,11 +6616,11 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ApplicationCallTxnFields.ForeignAssets))) } - for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { - o = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].MarshalMsg(o) + for zb0004 := range (*z).ApplicationCallTxnFields.ForeignAssets { + o = (*z).ApplicationCallTxnFields.ForeignAssets[zb0004].MarshalMsg(o) } } - if (zb0007Mask & 0x40000) == 0 { // if not empty + if (zb0008Mask & 0x80000) == 0 { // if not empty // string "apat" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x74) if (*z).ApplicationCallTxnFields.Accounts == nil { @@ -5496,7 +6632,7 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).ApplicationCallTxnFields.Accounts[zb0003].MarshalMsg(o) } } - if (zb0007Mask & 0x80000) == 0 { // if not empty + if (zb0008Mask & 0x100000) == 0 { // if not empty // string "apbx" o = append(o, 0xa4, 0x61, 0x70, 0x62, 0x78) if (*z).ApplicationCallTxnFields.Boxes == nil { @@ -5504,38 +6640,38 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ApplicationCallTxnFields.Boxes))) } - for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { + for zb0007 := range (*z).ApplicationCallTxnFields.Boxes { // omitempty: check for empty values - zb0008Len := uint32(2) - var zb0008Mask uint8 /* 3 bits */ - if (*z).ApplicationCallTxnFields.Boxes[zb0005].Index == 0 { - zb0008Len-- - zb0008Mask |= 0x2 - } - if len((*z).ApplicationCallTxnFields.Boxes[zb0005].Name) == 0 { - zb0008Len-- - zb0008Mask |= 0x4 - } - // variable map header, size zb0008Len - o = append(o, 0x80|uint8(zb0008Len)) - if (zb0008Mask & 0x2) == 0 { // if not empty + zb0009Len := uint32(2) + var zb0009Mask uint8 /* 3 bits */ + if (*z).ApplicationCallTxnFields.Boxes[zb0007].Index == 0 { + zb0009Len-- + zb0009Mask |= 0x2 + } + if len((*z).ApplicationCallTxnFields.Boxes[zb0007].Name) == 0 { + zb0009Len-- + zb0009Mask |= 0x4 + } + // variable map header, size zb0009Len + o = append(o, 0x80|uint8(zb0009Len)) + if (zb0009Mask & 0x2) == 0 { // if not empty // string "i" o = append(o, 0xa1, 0x69) - o = msgp.AppendUint64(o, (*z).ApplicationCallTxnFields.Boxes[zb0005].Index) + o = msgp.AppendUint64(o, (*z).ApplicationCallTxnFields.Boxes[zb0007].Index) } - if (zb0008Mask & 0x4) == 0 { // if not empty + if (zb0009Mask & 0x4) == 0 { // if not empty // string "n" o = append(o, 0xa1, 0x6e) - o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.Boxes[zb0007].Name) } } } - if (zb0007Mask & 0x100000) == 0 { // if not empty + if (zb0008Mask & 0x200000) == 0 { // if not empty // string "apep" o = append(o, 0xa4, 0x61, 0x70, 0x65, 0x70) o = msgp.AppendUint32(o, (*z).ApplicationCallTxnFields.ExtraProgramPages) } - if (zb0007Mask & 0x200000) == 0 { // if not empty + if (zb0008Mask & 0x400000) == 0 { // if not empty // string "apfa" o = append(o, 0xa4, 0x61, 0x70, 0x66, 0x61) if (*z).ApplicationCallTxnFields.ForeignApps == nil { @@ -5543,91 +6679,91 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ApplicationCallTxnFields.ForeignApps))) } - for zb0004 := range (*z).ApplicationCallTxnFields.ForeignApps { - o = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].MarshalMsg(o) + for zb0005 := range (*z).ApplicationCallTxnFields.ForeignApps { + o = (*z).ApplicationCallTxnFields.ForeignApps[zb0005].MarshalMsg(o) } } - if (zb0007Mask & 0x400000) == 0 { // if not empty + if (zb0008Mask & 0x800000) == 0 { // if not empty // string "apgs" o = append(o, 0xa4, 0x61, 0x70, 0x67, 0x73) o = (*z).ApplicationCallTxnFields.GlobalStateSchema.MarshalMsg(o) } - if (zb0007Mask & 0x800000) == 0 { // if not empty + if (zb0008Mask & 0x1000000) == 0 { // if not empty // string "apid" o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) o = (*z).ApplicationCallTxnFields.ApplicationID.MarshalMsg(o) } - if (zb0007Mask & 0x1000000) == 0 { // if not empty + if (zb0008Mask & 0x2000000) == 0 { // if not empty // string "apls" o = append(o, 0xa4, 0x61, 0x70, 0x6c, 0x73) o = (*z).ApplicationCallTxnFields.LocalStateSchema.MarshalMsg(o) } - if (zb0007Mask & 0x2000000) == 0 { // if not empty + if (zb0008Mask & 0x4000000) == 0 { // if not empty // string "aprv" o = append(o, 0xa4, 0x61, 0x70, 0x72, 0x76) o = msgp.AppendUint64(o, (*z).ApplicationCallTxnFields.RejectVersion) } - if (zb0007Mask & 0x4000000) == 0 { // if not empty + if (zb0008Mask & 0x8000000) == 0 { // if not empty // string "apsu" o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75) o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ClearStateProgram) } - if (zb0007Mask & 0x8000000) == 0 { // if not empty + if (zb0008Mask & 0x10000000) == 0 { // if not empty // string "arcv" o = append(o, 0xa4, 0x61, 0x72, 0x63, 0x76) o = (*z).AssetTransferTxnFields.AssetReceiver.MarshalMsg(o) } - if (zb0007Mask & 0x10000000) == 0 { // if not empty + if (zb0008Mask & 0x20000000) == 0 { // if not empty // string "asnd" o = append(o, 0xa4, 0x61, 0x73, 0x6e, 0x64) o = (*z).AssetTransferTxnFields.AssetSender.MarshalMsg(o) } - if (zb0007Mask & 0x20000000) == 0 { // if not empty + if (zb0008Mask & 0x40000000) == 0 { // if not empty // string "caid" o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64) o = (*z).AssetConfigTxnFields.ConfigAsset.MarshalMsg(o) } - if (zb0007Mask & 0x40000000) == 0 { // if not empty + if (zb0008Mask & 0x80000000) == 0 { // if not empty // string "close" o = append(o, 0xa5, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).PaymentTxnFields.CloseRemainderTo.MarshalMsg(o) } - if (zb0007Mask & 0x80000000) == 0 { // if not empty + if (zb0008Mask & 0x100000000) == 0 { // if not empty // string "fadd" o = append(o, 0xa4, 0x66, 0x61, 0x64, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAccount.MarshalMsg(o) } - if (zb0007Mask & 0x100000000) == 0 { // if not empty + if (zb0008Mask & 0x200000000) == 0 { // if not empty // string "faid" o = append(o, 0xa4, 0x66, 0x61, 0x69, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAsset.MarshalMsg(o) } - if (zb0007Mask & 0x200000000) == 0 { // if not empty + if (zb0008Mask & 0x400000000) == 0 { // if not empty // string "fee" o = append(o, 0xa3, 0x66, 0x65, 0x65) o = (*z).Header.Fee.MarshalMsg(o) } - if (zb0007Mask & 0x400000000) == 0 { // if not empty + if (zb0008Mask & 0x800000000) == 0 { // if not empty // string "fv" o = append(o, 0xa2, 0x66, 0x76) o = (*z).Header.FirstValid.MarshalMsg(o) } - if (zb0007Mask & 0x800000000) == 0 { // if not empty + if (zb0008Mask & 0x1000000000) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).Header.GenesisID) } - if (zb0007Mask & 0x1000000000) == 0 { // if not empty + if (zb0008Mask & 0x2000000000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).Header.GenesisHash.MarshalMsg(o) } - if (zb0007Mask & 0x2000000000) == 0 { // if not empty + if (zb0008Mask & 0x4000000000) == 0 { // if not empty // string "grp" o = append(o, 0xa3, 0x67, 0x72, 0x70) o = (*z).Header.Group.MarshalMsg(o) } - if (zb0007Mask & 0x4000000000) == 0 { // if not empty + if (zb0008Mask & 0x8000000000) == 0 { // if not empty // string "hb" o = append(o, 0xa2, 0x68, 0x62) if (*z).HeartbeatTxnFields == nil { @@ -5636,92 +6772,92 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).HeartbeatTxnFields.MarshalMsg(o) } } - if (zb0007Mask & 0x8000000000) == 0 { // if not empty + if (zb0008Mask & 0x10000000000) == 0 { // if not empty // string "lv" o = append(o, 0xa2, 0x6c, 0x76) o = (*z).Header.LastValid.MarshalMsg(o) } - if (zb0007Mask & 0x10000000000) == 0 { // if not empty + if (zb0008Mask & 0x20000000000) == 0 { // if not empty // string "lx" o = append(o, 0xa2, 0x6c, 0x78) o = msgp.AppendBytes(o, ((*z).Header.Lease)[:]) } - if (zb0007Mask & 0x20000000000) == 0 { // if not empty + if (zb0008Mask & 0x40000000000) == 0 { // if not empty // string "nonpart" o = append(o, 0xa7, 0x6e, 0x6f, 0x6e, 0x70, 0x61, 0x72, 0x74) o = msgp.AppendBool(o, (*z).KeyregTxnFields.Nonparticipation) } - if (zb0007Mask & 0x40000000000) == 0 { // if not empty + if (zb0008Mask & 0x80000000000) == 0 { // if not empty // string "note" o = append(o, 0xa4, 0x6e, 0x6f, 0x74, 0x65) o = msgp.AppendBytes(o, (*z).Header.Note) } - if (zb0007Mask & 0x80000000000) == 0 { // if not empty + if (zb0008Mask & 0x100000000000) == 0 { // if not empty // string "rcv" o = append(o, 0xa3, 0x72, 0x63, 0x76) o = (*z).PaymentTxnFields.Receiver.MarshalMsg(o) } - if (zb0007Mask & 0x100000000000) == 0 { // if not empty + if (zb0008Mask & 0x200000000000) == 0 { // if not empty // string "rekey" o = append(o, 0xa5, 0x72, 0x65, 0x6b, 0x65, 0x79) o = (*z).Header.RekeyTo.MarshalMsg(o) } - if (zb0007Mask & 0x200000000000) == 0 { // if not empty + if (zb0008Mask & 0x400000000000) == 0 { // if not empty // string "selkey" o = append(o, 0xa6, 0x73, 0x65, 0x6c, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.SelectionPK.MarshalMsg(o) } - if (zb0007Mask & 0x400000000000) == 0 { // if not empty + if (zb0008Mask & 0x800000000000) == 0 { // if not empty // string "snd" o = append(o, 0xa3, 0x73, 0x6e, 0x64) o = (*z).Header.Sender.MarshalMsg(o) } - if (zb0007Mask & 0x800000000000) == 0 { // if not empty + if (zb0008Mask & 0x1000000000000) == 0 { // if not empty // string "sp" o = append(o, 0xa2, 0x73, 0x70) o = (*z).StateProofTxnFields.StateProof.MarshalMsg(o) } - if (zb0007Mask & 0x1000000000000) == 0 { // if not empty + if (zb0008Mask & 0x2000000000000) == 0 { // if not empty // string "spmsg" o = append(o, 0xa5, 0x73, 0x70, 0x6d, 0x73, 0x67) o = (*z).StateProofTxnFields.Message.MarshalMsg(o) } - if (zb0007Mask & 0x2000000000000) == 0 { // if not empty + if (zb0008Mask & 0x4000000000000) == 0 { // if not empty // string "sprfkey" o = append(o, 0xa7, 0x73, 0x70, 0x72, 0x66, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.StateProofPK.MarshalMsg(o) } - if (zb0007Mask & 0x4000000000000) == 0 { // if not empty + if (zb0008Mask & 0x8000000000000) == 0 { // if not empty // string "sptype" o = append(o, 0xa6, 0x73, 0x70, 0x74, 0x79, 0x70, 0x65) o = (*z).StateProofTxnFields.StateProofType.MarshalMsg(o) } - if (zb0007Mask & 0x8000000000000) == 0 { // if not empty + if (zb0008Mask & 0x10000000000000) == 0 { // if not empty // string "type" o = append(o, 0xa4, 0x74, 0x79, 0x70, 0x65) o = (*z).Type.MarshalMsg(o) } - if (zb0007Mask & 0x10000000000000) == 0 { // if not empty + if (zb0008Mask & 0x20000000000000) == 0 { // if not empty // string "votefst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x66, 0x73, 0x74) o = (*z).KeyregTxnFields.VoteFirst.MarshalMsg(o) } - if (zb0007Mask & 0x20000000000000) == 0 { // if not empty + if (zb0008Mask & 0x40000000000000) == 0 { // if not empty // string "votekd" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x64) o = msgp.AppendUint64(o, (*z).KeyregTxnFields.VoteKeyDilution) } - if (zb0007Mask & 0x40000000000000) == 0 { // if not empty + if (zb0008Mask & 0x80000000000000) == 0 { // if not empty // string "votekey" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.VotePK.MarshalMsg(o) } - if (zb0007Mask & 0x80000000000000) == 0 { // if not empty + if (zb0008Mask & 0x100000000000000) == 0 { // if not empty // string "votelst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6c, 0x73, 0x74) o = (*z).KeyregTxnFields.VoteLast.MarshalMsg(o) } - if (zb0007Mask & 0x100000000000000) == 0 { // if not empty + if (zb0008Mask & 0x200000000000000) == 0 { // if not empty // string "xaid" o = append(o, 0xa4, 0x78, 0x61, 0x69, 0x64) o = (*z).AssetTransferTxnFields.XferAsset.MarshalMsg(o) @@ -5744,65 +6880,65 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) st.AllowableDepth-- var field []byte _ = field - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0008, zb0009, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Type.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Type") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.Sender.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sender") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.Fee.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Fee") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.FirstValid.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FirstValid") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.LastValid.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LastValid") return } } - if zb0007 > 0 { - zb0007-- - var zb0009 int - zb0009, err = msgp.ReadBytesBytesHeader(bts) + if zb0008 > 0 { + zb0008-- + var zb0010 int + zb0010, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Note") return } - if zb0009 > bounds.MaxTxnNoteBytes { - err = msgp.ErrOverflow(uint64(zb0009), uint64(bounds.MaxTxnNoteBytes)) + if zb0010 > bounds.MaxTxnNoteBytes { + err = msgp.ErrOverflow(uint64(zb0010), uint64(bounds.MaxTxnNoteBytes)) return } (*z).Header.Note, bts, err = msgp.ReadBytesBytes(bts, (*z).Header.Note) @@ -5811,16 +6947,16 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } - if zb0007 > 0 { - zb0007-- - var zb0010 int - zb0010, err = msgp.ReadBytesBytesHeader(bts) + if zb0008 > 0 { + zb0008-- + var zb0011 int + zb0011, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } - if zb0010 > bounds.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0010), uint64(bounds.MaxGenesisIDLen)) + if zb0011 > bounds.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0011), uint64(bounds.MaxGenesisIDLen)) return } (*z).Header.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -5829,238 +6965,238 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.GenesisHash.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.Group.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Group") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = msgp.ReadExactBytes(bts, ((*z).Header.Lease)[:]) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Lease") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).Header.RekeyTo.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RekeyTo") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).KeyregTxnFields.VotePK.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VotePK") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).KeyregTxnFields.SelectionPK.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "SelectionPK") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).KeyregTxnFields.StateProofPK.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofPK") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).KeyregTxnFields.VoteFirst.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VoteFirst") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).KeyregTxnFields.VoteLast.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VoteLast") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- (*z).KeyregTxnFields.VoteKeyDilution, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VoteKeyDilution") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- (*z).KeyregTxnFields.Nonparticipation, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Nonparticipation") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).PaymentTxnFields.Receiver.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Receiver") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).PaymentTxnFields.Amount.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Amount") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).PaymentTxnFields.CloseRemainderTo.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CloseRemainderTo") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetConfigTxnFields.ConfigAsset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ConfigAsset") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetConfigTxnFields.AssetParams.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetParams") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetTransferTxnFields.XferAsset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "XferAsset") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- (*z).AssetTransferTxnFields.AssetAmount, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetAmount") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetTransferTxnFields.AssetSender.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetSender") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetTransferTxnFields.AssetReceiver.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetReceiver") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetTransferTxnFields.AssetCloseTo.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetCloseTo") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetFreezeTxnFields.FreezeAccount.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FreezeAccount") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).AssetFreezeTxnFields.FreezeAsset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FreezeAsset") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- (*z).AssetFreezeTxnFields.AssetFrozen, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetFrozen") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).ApplicationCallTxnFields.ApplicationID.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationID") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- { - var zb0011 uint64 - zb0011, bts, err = msgp.ReadUint64Bytes(bts) + var zb0012 uint64 + zb0012, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OnCompletion") return } - (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0011) + (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0012) } } - if zb0007 > 0 { - zb0007-- - var zb0012 int - var zb0013 bool - zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0008 > 0 { + zb0008-- + var zb0013 int + var zb0014 bool + zb0013, zb0014, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0012 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0012), uint64(encodedMaxApplicationArgs)) + if zb0013 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0013), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0013 { + if zb0014 { (*z).ApplicationCallTxnFields.ApplicationArgs = nil - } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0012 { - (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0012] + } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0013 { + (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0013] } else { - (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0012) + (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0013) } for zb0002 := range (*z).ApplicationCallTxnFields.ApplicationArgs { (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) @@ -6070,26 +7206,26 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } } } - if zb0007 > 0 { - zb0007-- - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0008 > 0 { + zb0008-- + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0014 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedMaxAccounts)) + if zb0015 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0015), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0015 { + if zb0016 { (*z).ApplicationCallTxnFields.Accounts = nil - } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0014 { - (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0014] + } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0015 { + (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0015] } else { - (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0014) + (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0015) } for zb0003 := range (*z).ApplicationCallTxnFields.Accounts { bts, err = (*z).ApplicationCallTxnFields.Accounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -6099,141 +7235,199 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } } } - if zb0007 > 0 { - zb0007-- - var zb0016 int - var zb0017 bool - zb0016, zb0017, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0008 > 0 { + zb0008-- + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") + return + } + if zb0017 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0017), uint64(encodedMaxForeignAssets)) + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") + return + } + if zb0018 { + (*z).ApplicationCallTxnFields.ForeignAssets = nil + } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0017 { + (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0017] + } else { + (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0017) + } + for zb0004 := range (*z).ApplicationCallTxnFields.ForeignAssets { + bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0004) + return + } + } + } + if zb0008 > 0 { + zb0008-- + var zb0019 int + var zb0020 bool + zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0016 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxForeignApps)) + if zb0019 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0019), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0017 { + if zb0020 { (*z).ApplicationCallTxnFields.ForeignApps = nil - } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0016 { - (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0016] + } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0019 { + (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0019] } else { - (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0016) + (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0019) } - for zb0004 := range (*z).ApplicationCallTxnFields.ForeignApps { - bts, err = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].UnmarshalMsgWithState(bts, st) + for zb0005 := range (*z).ApplicationCallTxnFields.ForeignApps { + bts, err = (*z).ApplicationCallTxnFields.ForeignApps[zb0005].UnmarshalMsgWithState(bts, st) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignApps", zb0004) + err = msgp.WrapError(err, "struct-from-array", "ForeignApps", zb0005) return } } } - if zb0007 > 0 { - zb0007-- - var zb0018 int - var zb0019 bool - zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0008 > 0 { + zb0008-- + var zb0021 int + var zb0022 bool + zb0021, zb0022, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Access") + return + } + if zb0021 > encodedMaxAccess { + err = msgp.ErrOverflow(uint64(zb0021), uint64(encodedMaxAccess)) + err = msgp.WrapError(err, "struct-from-array", "Access") + return + } + if zb0022 { + (*z).ApplicationCallTxnFields.Access = nil + } else if (*z).ApplicationCallTxnFields.Access != nil && cap((*z).ApplicationCallTxnFields.Access) >= zb0021 { + (*z).ApplicationCallTxnFields.Access = ((*z).ApplicationCallTxnFields.Access)[:zb0021] + } else { + (*z).ApplicationCallTxnFields.Access = make([]ResourceRef, zb0021) + } + for zb0006 := range (*z).ApplicationCallTxnFields.Access { + bts, err = (*z).ApplicationCallTxnFields.Access[zb0006].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Access", zb0006) + return + } + } + } + if zb0008 > 0 { + zb0008-- + var zb0023 int + var zb0024 bool + zb0023, zb0024, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Boxes") return } - if zb0018 > encodedMaxBoxes { - err = msgp.ErrOverflow(uint64(zb0018), uint64(encodedMaxBoxes)) + if zb0023 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0023), uint64(encodedMaxBoxes)) err = msgp.WrapError(err, "struct-from-array", "Boxes") return } - if zb0019 { + if zb0024 { (*z).ApplicationCallTxnFields.Boxes = nil - } else if (*z).ApplicationCallTxnFields.Boxes != nil && cap((*z).ApplicationCallTxnFields.Boxes) >= zb0018 { - (*z).ApplicationCallTxnFields.Boxes = ((*z).ApplicationCallTxnFields.Boxes)[:zb0018] + } else if (*z).ApplicationCallTxnFields.Boxes != nil && cap((*z).ApplicationCallTxnFields.Boxes) >= zb0023 { + (*z).ApplicationCallTxnFields.Boxes = ((*z).ApplicationCallTxnFields.Boxes)[:zb0023] } else { - (*z).ApplicationCallTxnFields.Boxes = make([]BoxRef, zb0018) + (*z).ApplicationCallTxnFields.Boxes = make([]BoxRef, zb0023) } - for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { - var zb0020 int - var zb0021 bool - zb0020, zb0021, bts, err = msgp.ReadMapHeaderBytes(bts) + for zb0007 := range (*z).ApplicationCallTxnFields.Boxes { + var zb0025 int + var zb0026 bool + zb0025, zb0026, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0025, zb0026, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007) return } - if zb0020 > 0 { - zb0020-- - (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + if zb0025 > 0 { + zb0025-- + (*z).ApplicationCallTxnFields.Boxes[zb0007].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array", "Index") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "struct-from-array", "Index") return } } - if zb0020 > 0 { - zb0020-- - var zb0022 int - zb0022, err = msgp.ReadBytesBytesHeader(bts) + if zb0025 > 0 { + zb0025-- + var zb0027 int + zb0027, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array", "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "struct-from-array", "Name") return } - if zb0022 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0022), uint64(bounds.MaxBytesKeyValueLen)) + if zb0027 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0027), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + (*z).ApplicationCallTxnFields.Boxes[zb0007].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0007].Name) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array", "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "struct-from-array", "Name") return } } - if zb0020 > 0 { - err = msgp.ErrTooManyArrayFields(zb0020) + if zb0025 > 0 { + err = msgp.ErrTooManyArrayFields(zb0025) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "struct-from-array") return } } } else { if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007) return } - if zb0021 { - (*z).ApplicationCallTxnFields.Boxes[zb0005] = BoxRef{} + if zb0026 { + (*z).ApplicationCallTxnFields.Boxes[zb0007] = BoxRef{} } - for zb0020 > 0 { - zb0020-- + for zb0025 > 0 { + zb0025-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007) return } switch string(field) { case "i": - (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ApplicationCallTxnFields.Boxes[zb0007].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "Index") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "Index") return } case "n": - var zb0023 int - zb0023, err = msgp.ReadBytesBytesHeader(bts) + var zb0028 int + zb0028, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "Name") return } - if zb0023 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0023), uint64(bounds.MaxBytesKeyValueLen)) + if zb0028 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0028), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + (*z).ApplicationCallTxnFields.Boxes[zb0007].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0007].Name) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "Name") + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007, "Name") return } default: err = msgp.ErrNoField(string(field)) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0007) return } } @@ -6241,61 +7435,32 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } } } - if zb0007 > 0 { - zb0007-- - var zb0024 int - var zb0025 bool - zb0024, zb0025, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") - return - } - if zb0024 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0024), uint64(encodedMaxForeignAssets)) - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") - return - } - if zb0025 { - (*z).ApplicationCallTxnFields.ForeignAssets = nil - } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0024 { - (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0024] - } else { - (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0024) - } - for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { - bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].UnmarshalMsgWithState(bts, st) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0006) - return - } - } - } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).ApplicationCallTxnFields.LocalStateSchema.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LocalStateSchema") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).ApplicationCallTxnFields.GlobalStateSchema.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GlobalStateSchema") return } } - if zb0007 > 0 { - zb0007-- - var zb0026 int - zb0026, err = msgp.ReadBytesBytesHeader(bts) + if zb0008 > 0 { + zb0008-- + var zb0029 int + zb0029, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApprovalProgram") return } - if zb0026 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0026), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0029 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0029), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApprovalProgram) @@ -6304,16 +7469,16 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } - if zb0007 > 0 { - zb0007-- - var zb0027 int - zb0027, err = msgp.ReadBytesBytesHeader(bts) + if zb0008 > 0 { + zb0008-- + var zb0030 int + zb0030, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ClearStateProgram") return } - if zb0027 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0027), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0030 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0030), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ClearStateProgram) @@ -6322,48 +7487,48 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- (*z).ApplicationCallTxnFields.ExtraProgramPages, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExtraProgramPages") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- (*z).ApplicationCallTxnFields.RejectVersion, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RejectVersion") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).StateProofTxnFields.StateProofType.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofType") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).StateProofTxnFields.StateProof.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProof") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- bts, err = (*z).StateProofTxnFields.Message.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Message") return } } - if zb0007 > 0 { - zb0007-- + if zb0008 > 0 { + zb0008-- if msgp.IsNil(bts) { bts, err = msgp.ReadNilBytes(bts) if err != nil { @@ -6381,8 +7546,8 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } } } - if zb0007 > 0 { - err = msgp.ErrTooManyArrayFields(zb0007) + if zb0008 > 0 { + err = msgp.ErrTooManyArrayFields(zb0008) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -6393,11 +7558,11 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err) return } - if zb0008 { + if zb0009 { (*z) = Transaction{} } - for zb0007 > 0 { - zb0007-- + for zb0008 > 0 { + zb0008-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -6435,14 +7600,14 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } case "note": - var zb0028 int - zb0028, err = msgp.ReadBytesBytesHeader(bts) + var zb0031 int + zb0031, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "Note") return } - if zb0028 > bounds.MaxTxnNoteBytes { - err = msgp.ErrOverflow(uint64(zb0028), uint64(bounds.MaxTxnNoteBytes)) + if zb0031 > bounds.MaxTxnNoteBytes { + err = msgp.ErrOverflow(uint64(zb0031), uint64(bounds.MaxTxnNoteBytes)) return } (*z).Header.Note, bts, err = msgp.ReadBytesBytes(bts, (*z).Header.Note) @@ -6451,14 +7616,14 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } case "gen": - var zb0029 int - zb0029, err = msgp.ReadBytesBytesHeader(bts) + var zb0032 int + zb0032, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "GenesisID") return } - if zb0029 > bounds.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0029), uint64(bounds.MaxGenesisIDLen)) + if zb0032 > bounds.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0032), uint64(bounds.MaxGenesisIDLen)) return } (*z).Header.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -6618,33 +7783,33 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } case "apan": { - var zb0030 uint64 - zb0030, bts, err = msgp.ReadUint64Bytes(bts) + var zb0033 uint64 + zb0033, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OnCompletion") return } - (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0030) + (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0033) } case "apaa": - var zb0031 int - var zb0032 bool - zb0031, zb0032, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0034 int + var zb0035 bool + zb0034, zb0035, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0031 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0031), uint64(encodedMaxApplicationArgs)) + if zb0034 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0034), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0032 { + if zb0035 { (*z).ApplicationCallTxnFields.ApplicationArgs = nil - } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0031 { - (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0031] + } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0034 { + (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0034] } else { - (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0031) + (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0034) } for zb0002 := range (*z).ApplicationCallTxnFields.ApplicationArgs { (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) @@ -6654,24 +7819,24 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } } case "apat": - var zb0033 int - var zb0034 bool - zb0033, zb0034, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0036 int + var zb0037 bool + zb0036, zb0037, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Accounts") return } - if zb0033 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0033), uint64(encodedMaxAccounts)) + if zb0036 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0036), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "Accounts") return } - if zb0034 { + if zb0037 { (*z).ApplicationCallTxnFields.Accounts = nil - } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0033 { - (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0033] + } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0036 { + (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0036] } else { - (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0033) + (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0036) } for zb0003 := range (*z).ApplicationCallTxnFields.Accounts { bts, err = (*z).ApplicationCallTxnFields.Accounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -6680,171 +7845,198 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } + case "apas": + var zb0038 int + var zb0039 bool + zb0038, zb0039, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "ForeignAssets") + return + } + if zb0038 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0038), uint64(encodedMaxForeignAssets)) + err = msgp.WrapError(err, "ForeignAssets") + return + } + if zb0039 { + (*z).ApplicationCallTxnFields.ForeignAssets = nil + } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0038 { + (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0038] + } else { + (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0038) + } + for zb0004 := range (*z).ApplicationCallTxnFields.ForeignAssets { + bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ForeignAssets", zb0004) + return + } + } case "apfa": - var zb0035 int - var zb0036 bool - zb0035, zb0036, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0040 int + var zb0041 bool + zb0040, zb0041, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ForeignApps") return } - if zb0035 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0035), uint64(encodedMaxForeignApps)) + if zb0040 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0040), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "ForeignApps") return } - if zb0036 { + if zb0041 { (*z).ApplicationCallTxnFields.ForeignApps = nil - } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0035 { - (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0035] + } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0040 { + (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0040] } else { - (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0035) + (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0040) } - for zb0004 := range (*z).ApplicationCallTxnFields.ForeignApps { - bts, err = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].UnmarshalMsgWithState(bts, st) + for zb0005 := range (*z).ApplicationCallTxnFields.ForeignApps { + bts, err = (*z).ApplicationCallTxnFields.ForeignApps[zb0005].UnmarshalMsgWithState(bts, st) if err != nil { - err = msgp.WrapError(err, "ForeignApps", zb0004) + err = msgp.WrapError(err, "ForeignApps", zb0005) + return + } + } + case "al": + var zb0042 int + var zb0043 bool + zb0042, zb0043, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Access") + return + } + if zb0042 > encodedMaxAccess { + err = msgp.ErrOverflow(uint64(zb0042), uint64(encodedMaxAccess)) + err = msgp.WrapError(err, "Access") + return + } + if zb0043 { + (*z).ApplicationCallTxnFields.Access = nil + } else if (*z).ApplicationCallTxnFields.Access != nil && cap((*z).ApplicationCallTxnFields.Access) >= zb0042 { + (*z).ApplicationCallTxnFields.Access = ((*z).ApplicationCallTxnFields.Access)[:zb0042] + } else { + (*z).ApplicationCallTxnFields.Access = make([]ResourceRef, zb0042) + } + for zb0006 := range (*z).ApplicationCallTxnFields.Access { + bts, err = (*z).ApplicationCallTxnFields.Access[zb0006].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Access", zb0006) return } } case "apbx": - var zb0037 int - var zb0038 bool - zb0037, zb0038, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0044 int + var zb0045 bool + zb0044, zb0045, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Boxes") return } - if zb0037 > encodedMaxBoxes { - err = msgp.ErrOverflow(uint64(zb0037), uint64(encodedMaxBoxes)) + if zb0044 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0044), uint64(encodedMaxBoxes)) err = msgp.WrapError(err, "Boxes") return } - if zb0038 { + if zb0045 { (*z).ApplicationCallTxnFields.Boxes = nil - } else if (*z).ApplicationCallTxnFields.Boxes != nil && cap((*z).ApplicationCallTxnFields.Boxes) >= zb0037 { - (*z).ApplicationCallTxnFields.Boxes = ((*z).ApplicationCallTxnFields.Boxes)[:zb0037] + } else if (*z).ApplicationCallTxnFields.Boxes != nil && cap((*z).ApplicationCallTxnFields.Boxes) >= zb0044 { + (*z).ApplicationCallTxnFields.Boxes = ((*z).ApplicationCallTxnFields.Boxes)[:zb0044] } else { - (*z).ApplicationCallTxnFields.Boxes = make([]BoxRef, zb0037) + (*z).ApplicationCallTxnFields.Boxes = make([]BoxRef, zb0044) } - for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { - var zb0039 int - var zb0040 bool - zb0039, zb0040, bts, err = msgp.ReadMapHeaderBytes(bts) + for zb0007 := range (*z).ApplicationCallTxnFields.Boxes { + var zb0046 int + var zb0047 bool + zb0046, zb0047, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0039, zb0040, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0046, zb0047, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005) + err = msgp.WrapError(err, "Boxes", zb0007) return } - if zb0039 > 0 { - zb0039-- - (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + if zb0046 > 0 { + zb0046-- + (*z).ApplicationCallTxnFields.Boxes[zb0007].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array", "Index") + err = msgp.WrapError(err, "Boxes", zb0007, "struct-from-array", "Index") return } } - if zb0039 > 0 { - zb0039-- - var zb0041 int - zb0041, err = msgp.ReadBytesBytesHeader(bts) + if zb0046 > 0 { + zb0046-- + var zb0048 int + zb0048, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array", "Name") + err = msgp.WrapError(err, "Boxes", zb0007, "struct-from-array", "Name") return } - if zb0041 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0041), uint64(bounds.MaxBytesKeyValueLen)) + if zb0048 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0048), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + (*z).ApplicationCallTxnFields.Boxes[zb0007].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0007].Name) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array", "Name") + err = msgp.WrapError(err, "Boxes", zb0007, "struct-from-array", "Name") return } } - if zb0039 > 0 { - err = msgp.ErrTooManyArrayFields(zb0039) + if zb0046 > 0 { + err = msgp.ErrTooManyArrayFields(zb0046) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array") + err = msgp.WrapError(err, "Boxes", zb0007, "struct-from-array") return } } } else { if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005) + err = msgp.WrapError(err, "Boxes", zb0007) return } - if zb0040 { - (*z).ApplicationCallTxnFields.Boxes[zb0005] = BoxRef{} + if zb0047 { + (*z).ApplicationCallTxnFields.Boxes[zb0007] = BoxRef{} } - for zb0039 > 0 { - zb0039-- + for zb0046 > 0 { + zb0046-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005) + err = msgp.WrapError(err, "Boxes", zb0007) return } switch string(field) { case "i": - (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ApplicationCallTxnFields.Boxes[zb0007].Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "Index") + err = msgp.WrapError(err, "Boxes", zb0007, "Index") return } case "n": - var zb0042 int - zb0042, err = msgp.ReadBytesBytesHeader(bts) + var zb0049 int + zb0049, err = msgp.ReadBytesBytesHeader(bts) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "Name") + err = msgp.WrapError(err, "Boxes", zb0007, "Name") return } - if zb0042 > bounds.MaxBytesKeyValueLen { - err = msgp.ErrOverflow(uint64(zb0042), uint64(bounds.MaxBytesKeyValueLen)) + if zb0049 > bounds.MaxBytesKeyValueLen { + err = msgp.ErrOverflow(uint64(zb0049), uint64(bounds.MaxBytesKeyValueLen)) return } - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + (*z).ApplicationCallTxnFields.Boxes[zb0007].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0007].Name) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005, "Name") + err = msgp.WrapError(err, "Boxes", zb0007, "Name") return } default: err = msgp.ErrNoField(string(field)) if err != nil { - err = msgp.WrapError(err, "Boxes", zb0005) + err = msgp.WrapError(err, "Boxes", zb0007) return } } } } } - case "apas": - var zb0043 int - var zb0044 bool - zb0043, zb0044, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "ForeignAssets") - return - } - if zb0043 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0043), uint64(encodedMaxForeignAssets)) - err = msgp.WrapError(err, "ForeignAssets") - return - } - if zb0044 { - (*z).ApplicationCallTxnFields.ForeignAssets = nil - } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0043 { - (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0043] - } else { - (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0043) - } - for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { - bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].UnmarshalMsgWithState(bts, st) - if err != nil { - err = msgp.WrapError(err, "ForeignAssets", zb0006) - return - } - } case "apls": bts, err = (*z).ApplicationCallTxnFields.LocalStateSchema.UnmarshalMsgWithState(bts, st) if err != nil { @@ -6858,14 +8050,14 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } case "apap": - var zb0045 int - zb0045, err = msgp.ReadBytesBytesHeader(bts) + var zb0050 int + zb0050, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ApprovalProgram") return } - if zb0045 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0045), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0050 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0050), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApprovalProgram) @@ -6874,14 +8066,14 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } case "apsu": - var zb0046 int - zb0046, err = msgp.ReadBytesBytesHeader(bts) + var zb0051 int + zb0051, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ClearStateProgram") return } - if zb0046 > bounds.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0046), uint64(bounds.MaxAvailableAppProgramLen)) + if zb0051 > bounds.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0051), uint64(bounds.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ClearStateProgram) @@ -6968,16 +8160,20 @@ func (z *Transaction) Msgsize() (s int) { s += (*z).ApplicationCallTxnFields.Accounts[zb0003].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0004 := range (*z).ApplicationCallTxnFields.ForeignApps { - s += (*z).ApplicationCallTxnFields.ForeignApps[zb0004].Msgsize() + for zb0004 := range (*z).ApplicationCallTxnFields.ForeignAssets { + s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0004].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { - s += 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + for zb0005 := range (*z).ApplicationCallTxnFields.ForeignApps { + s += (*z).ApplicationCallTxnFields.ForeignApps[zb0005].Msgsize() + } + s += 3 + msgp.ArrayHeaderSize + for zb0006 := range (*z).ApplicationCallTxnFields.Access { + s += (*z).ApplicationCallTxnFields.Access[zb0006].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { - s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].Msgsize() + for zb0007 := range (*z).ApplicationCallTxnFields.Boxes { + s += 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.Boxes[zb0007].Name) } s += 5 + (*z).ApplicationCallTxnFields.LocalStateSchema.Msgsize() + 5 + (*z).ApplicationCallTxnFields.GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ClearStateProgram) + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size + 7 + (*z).StateProofTxnFields.StateProofType.Msgsize() + 3 + (*z).StateProofTxnFields.StateProof.Msgsize() + 6 + (*z).StateProofTxnFields.Message.Msgsize() + 3 if (*z).HeartbeatTxnFields == nil { @@ -6990,7 +8186,7 @@ func (z *Transaction) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *Transaction) MsgIsZero() bool { - return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).ApplicationCallTxnFields.RejectVersion == 0) && ((*z).StateProofTxnFields.StateProofType.MsgIsZero()) && ((*z).StateProofTxnFields.StateProof.MsgIsZero()) && ((*z).StateProofTxnFields.Message.MsgIsZero()) && ((*z).HeartbeatTxnFields == nil) + return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Access) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).ApplicationCallTxnFields.RejectVersion == 0) && ((*z).StateProofTxnFields.StateProofType.MsgIsZero()) && ((*z).StateProofTxnFields.StateProof.MsgIsZero()) && ((*z).StateProofTxnFields.Message.MsgIsZero()) && ((*z).HeartbeatTxnFields == nil) } // MaxSize returns a maximum valid message size for this message type @@ -7004,14 +8200,17 @@ func TransactionMaxSize() (s int) { // Calculating size of slice: z.ApplicationCallTxnFields.Accounts s += msgp.ArrayHeaderSize + ((encodedMaxAccounts) * (basics.AddressMaxSize())) s += 5 + // Calculating size of slice: z.ApplicationCallTxnFields.ForeignAssets + s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize())) + s += 5 // Calculating size of slice: z.ApplicationCallTxnFields.ForeignApps s += msgp.ArrayHeaderSize + ((encodedMaxForeignApps) * (basics.AppIndexMaxSize())) + s += 3 + // Calculating size of slice: z.ApplicationCallTxnFields.Access + s += msgp.ArrayHeaderSize + ((encodedMaxAccess) * (ResourceRefMaxSize())) s += 5 // Calculating size of slice: z.ApplicationCallTxnFields.Boxes s += msgp.ArrayHeaderSize + ((encodedMaxBoxes) * (BoxRefMaxSize())) - s += 5 - // Calculating size of slice: z.ApplicationCallTxnFields.ForeignAssets - s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize())) s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + bounds.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + bounds.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size + 7 + protocol.StateProofTypeMaxSize() + 3 + stateproof.StateProofMaxSize() + 6 + stateproofmsg.MessageMaxSize() + 3 s += HeartbeatTxnFieldsMaxSize() return diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index 49ed14f6e3..8cc316425c 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -554,6 +554,66 @@ func BenchmarkUnmarshalHeartbeatTxnFields(b *testing.B) { } } +func TestMarshalUnmarshalHoldingRef(t *testing.T) { + partitiontest.PartitionTest(t) + v := HoldingRef{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingHoldingRef(t *testing.T) { + protocol.RunEncodingTest(t, &HoldingRef{}) +} + +func BenchmarkMarshalMsgHoldingRef(b *testing.B) { + v := HoldingRef{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgHoldingRef(b *testing.B) { + v := HoldingRef{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalHoldingRef(b *testing.B) { + v := HoldingRef{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalKeyregTxnFields(t *testing.T) { partitiontest.PartitionTest(t) v := KeyregTxnFields{} @@ -614,6 +674,66 @@ func BenchmarkUnmarshalKeyregTxnFields(b *testing.B) { } } +func TestMarshalUnmarshalLocalsRef(t *testing.T) { + partitiontest.PartitionTest(t) + v := LocalsRef{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingLocalsRef(t *testing.T) { + protocol.RunEncodingTest(t, &LocalsRef{}) +} + +func BenchmarkMarshalMsgLocalsRef(b *testing.B) { + v := LocalsRef{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgLocalsRef(b *testing.B) { + v := LocalsRef{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalLocalsRef(b *testing.B) { + v := LocalsRef{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalLogicSig(t *testing.T) { partitiontest.PartitionTest(t) v := LogicSig{} @@ -794,6 +914,66 @@ func BenchmarkUnmarshalPayset(b *testing.B) { } } +func TestMarshalUnmarshalResourceRef(t *testing.T) { + partitiontest.PartitionTest(t) + v := ResourceRef{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingResourceRef(t *testing.T) { + protocol.RunEncodingTest(t, &ResourceRef{}) +} + +func BenchmarkMarshalMsgResourceRef(b *testing.B) { + v := ResourceRef{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgResourceRef(b *testing.B) { + v := ResourceRef{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalResourceRef(b *testing.B) { + v := ResourceRef{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalSignedTxn(t *testing.T) { partitiontest.PartitionTest(t) v := SignedTxn{} diff --git a/data/txntest/txn.go b/data/txntest/txn.go index c677a70d28..7b46a93d44 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -83,6 +83,7 @@ type Txn struct { ForeignApps []basics.AppIndex ForeignAssets []basics.AssetIndex Boxes []transactions.BoxRef + Access []transactions.ResourceRef LocalStateSchema basics.StateSchema GlobalStateSchema basics.StateSchema ApprovalProgram interface{} // string, nil, or []bytes if already compiled @@ -117,6 +118,10 @@ func (tx *Txn) internalCopy() { for i := 0; i < len(tx.Boxes); i++ { tx.Boxes[i].Name = append([]byte(nil), tx.Boxes[i].Name...) } + tx.Access = append([]transactions.ResourceRef(nil), tx.Access...) + for i := 0; i < len(tx.Access); i++ { + tx.Access[i].Box.Name = append([]byte(nil), tx.Access[i].Box.Name...) + } // Programs may or may not actually be byte slices. The other // possibilitiues don't require copies. @@ -288,6 +293,7 @@ func (tx Txn) Txn() transactions.Transaction { ForeignApps: append([]basics.AppIndex(nil), tx.ForeignApps...), ForeignAssets: append([]basics.AssetIndex(nil), tx.ForeignAssets...), Boxes: tx.Boxes, + Access: tx.Access, LocalStateSchema: tx.LocalStateSchema, GlobalStateSchema: tx.GlobalStateSchema, ApprovalProgram: assemble(tx.ApprovalProgram), diff --git a/ledger/apply/application.go b/ledger/apply/application.go index 8e65329b89..298668c3bd 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -311,13 +311,13 @@ func closeOutApplication(balances Balances, sender basics.Address, appIdx basics return nil } -func checkPrograms(ac *transactions.ApplicationCallTxnFields, evalParams *logic.EvalParams) error { - err := logic.CheckContract(ac.ApprovalProgram, evalParams) +func checkPrograms(ac *transactions.ApplicationCallTxnFields, gi int, evalParams *logic.EvalParams) error { + err := logic.CheckContract(ac.ApprovalProgram, gi, evalParams) if err != nil { return fmt.Errorf("check failed on ApprovalProgram: %v", err) } - err = logic.CheckContract(ac.ClearStateProgram, evalParams) + err = logic.CheckContract(ac.ClearStateProgram, gi, evalParams) if err != nil { return fmt.Errorf("check failed on ClearStateProgram: %v", err) } @@ -383,7 +383,7 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio return err } - err = checkPrograms(&ac, evalParams) + err = checkPrograms(&ac, gi, evalParams) if err != nil { return err } diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index f570a4b665..1437e02550 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -17,6 +17,7 @@ package apply import ( + "bytes" "fmt" "maps" "math/rand" @@ -34,62 +35,6 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestApplicationCallFieldsEmpty(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - ac := transactions.ApplicationCallTxnFields{} - a.True(ac.Empty()) - - ac.ApplicationID = 1 - a.False(ac.Empty()) - - ac.ApplicationID = 0 - ac.OnCompletion = 1 - a.False(ac.Empty()) - - ac.OnCompletion = 0 - ac.ApplicationArgs = make([][]byte, 1) - a.False(ac.Empty()) - - ac.ApplicationArgs = nil - ac.Accounts = make([]basics.Address, 1) - a.False(ac.Empty()) - - ac.Accounts = nil - ac.ForeignApps = make([]basics.AppIndex, 1) - a.False(ac.Empty()) - - ac.ForeignApps = nil - ac.LocalStateSchema = basics.StateSchema{NumUint: 1} - a.False(ac.Empty()) - - ac.LocalStateSchema = basics.StateSchema{} - ac.GlobalStateSchema = basics.StateSchema{NumUint: 1} - a.False(ac.Empty()) - - ac.GlobalStateSchema = basics.StateSchema{} - ac.ApprovalProgram = []byte{1} - a.False(ac.Empty()) - - ac.ApprovalProgram = []byte{} - a.False(ac.Empty()) - - ac.ApprovalProgram = nil - ac.ClearStateProgram = []byte{1} - a.False(ac.Empty()) - - ac.ClearStateProgram = []byte{} - a.False(ac.Empty()) - - ac.ClearStateProgram = nil - a.True(ac.Empty()) - - ac.ExtraProgramPages = 0 - a.True(ac.Empty()) -} - func getRandomAddress(a *require.Assertions) basics.Address { const rl = 16 b := make([]byte, rl) @@ -330,6 +275,7 @@ func (b *testBalances) SetParams(params config.ConsensusParams) { func TestAppCallGetParam(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -370,6 +316,7 @@ func TestAppCallGetParam(t *testing.T) { func TestAppCallAddressByIndex(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -393,44 +340,83 @@ func TestAppCallAddressByIndex(t *testing.T) { a.ErrorContains(err, "invalid Account reference 2") } -func TestAppCallCheckPrograms(t *testing.T) { +func TestAppCallCheckProgramCosts(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) var ac transactions.ApplicationCallTxnFields // This check is for static costs. v26 is last with static cost checking proto := config.Consensus[protocol.ConsensusV26] - ep := logic.NewAppEvalParams(nil, &proto, nil) + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) proto.MaxAppProgramCost = 1 - err := checkPrograms(&ac, ep) + err := checkPrograms(&ac, 0, ep) a.ErrorContains(err, "check failed on ApprovalProgram") program := []byte{2, 0x20, 1, 1, 0x22} // version, intcb, int 1 ac.ApprovalProgram = program ac.ClearStateProgram = program - err = checkPrograms(&ac, ep) + err = checkPrograms(&ac, 0, ep) a.ErrorContains(err, "check failed on ApprovalProgram") proto.MaxAppProgramCost = 10 - err = checkPrograms(&ac, ep) + err = checkPrograms(&ac, 0, ep) a.NoError(err) ac.ClearStateProgram = append(ac.ClearStateProgram, program...) ac.ClearStateProgram = append(ac.ClearStateProgram, program...) ac.ClearStateProgram = append(ac.ClearStateProgram, program...) - err = checkPrograms(&ac, ep) + err = checkPrograms(&ac, 0, ep) a.ErrorContains(err, "check failed on ClearStateProgram") ac.ClearStateProgram = program - err = checkPrograms(&ac, ep) + err = checkPrograms(&ac, 0, ep) a.NoError(err) } +func TestAppCallCheckProgramsWithAccess(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(t) + + var ac transactions.ApplicationCallTxnFields + for _, cv := range []protocol.ConsensusVersion{ + protocol.ConsensusCurrentVersion, + protocol.ConsensusFuture, + } { + proto := config.Consensus[cv] + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) + program := []byte{2, 0x20, 1, 1, 0x22} // version, intcb, int 1 + ac.ApprovalProgram = program + ac.ClearStateProgram = bytes.Clone(program) + + err := checkPrograms(&ac, 0, ep) + a.NoError(err) + + ep.TxnGroup[0].Txn.Access = []transactions.ResourceRef{{Address: basics.Address{0x01}}} + err = checkPrograms(&ac, 0, ep) + a.ErrorContains(err, "check failed on ApprovalProgram: pre-sharedResources program") + + ac.ApprovalProgram[0] = 9 + err = checkPrograms(&ac, 0, ep) + a.ErrorContains(err, "check failed on ClearStateProgram: pre-sharedResources program") + + ac.ClearStateProgram[0] = 9 + err = checkPrograms(&ac, 0, ep) + a.NoError(err) + } + +} + func TestAppCallCreate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -469,6 +455,7 @@ func TestAppCallCreate(t *testing.T) { // TestAppCallApplyCreate carefully tracks and validates balance record updates func TestAppCallApplyCreate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -485,7 +472,8 @@ func TestAppCallApplyCreate(t *testing.T) { b := newTestBalances() b.SetProto(protocol.ConsensusFuture) proto := b.ConsensusParams() - ep := logic.NewAppEvalParams(nil, &proto, nil) + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) var txnCounter uint64 = 1 @@ -566,6 +554,7 @@ func TestAppCallApplyCreate(t *testing.T) { // TestAppCallApplyCreateOptIn checks balance record fields without tracking substages func TestAppCallApplyCreateOptIn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -585,7 +574,8 @@ func TestAppCallApplyCreateOptIn(t *testing.T) { b := newTestBalancesPass() b.SetProto(protocol.ConsensusFuture) proto := b.ConsensusParams() - ep := logic.NewAppEvalParams(nil, &proto, nil) + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) var txnCounter uint64 = 1 appIdx := basics.AppIndex(txnCounter + 1) var ad *transactions.ApplyData = &transactions.ApplyData{} @@ -612,6 +602,7 @@ func TestAppCallApplyCreateOptIn(t *testing.T) { func TestAppCallOptIn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -728,6 +719,7 @@ func TestAppCallOptIn(t *testing.T) { func TestAppCallClearState(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -897,6 +889,7 @@ func TestAppCallClearState(t *testing.T) { func TestAppCallApplyCloseOut(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -992,6 +985,7 @@ func TestAppCallApplyCloseOut(t *testing.T) { func TestAppCallApplyUpdate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -1019,7 +1013,8 @@ func TestAppCallApplyUpdate(t *testing.T) { b := newTestBalances() b.SetProto(protocol.ConsensusV28) proto := b.ConsensusParams() - ep := logic.NewAppEvalParams(nil, &proto, nil) + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) b.balances = make(map[basics.Address]basics.AccountData) cbr := basics.AccountData{ @@ -1141,6 +1136,7 @@ func TestAppCallApplyUpdate(t *testing.T) { func TestAppCallApplyDelete(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -1262,6 +1258,7 @@ func TestAppCallApplyDelete(t *testing.T) { func TestAppCallApplyCreateClearState(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -1283,7 +1280,8 @@ func TestAppCallApplyCreateClearState(t *testing.T) { b := newTestBalancesPass() b.SetProto(protocol.ConsensusFuture) proto := b.ConsensusParams() - ep := logic.NewAppEvalParams(nil, &proto, nil) + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) b.balances = make(map[basics.Address]basics.AccountData) b.balances[creator] = basics.AccountData{} @@ -1311,6 +1309,7 @@ func TestAppCallApplyCreateClearState(t *testing.T) { func TestAppCallApplyCreateDelete(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -1332,7 +1331,8 @@ func TestAppCallApplyCreateDelete(t *testing.T) { b := newTestBalancesPass() b.SetProto(protocol.ConsensusFuture) proto := b.ConsensusParams() - ep := logic.NewAppEvalParams(nil, &proto, nil) + stads := []transactions.SignedTxnWithAD{{}} + ep := logic.NewAppEvalParams(stads, &proto, nil) b.balances = make(map[basics.Address]basics.AccountData) b.balances[creator] = basics.AccountData{} diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index d338382169..8f8047feaf 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -921,7 +921,7 @@ func TestInnerRekey(t *testing.T) { // the outer app could have opted-in. But this technique tests something // interesting, that the inner app can perform an opt-in on the outer app, which // tests that the newly created app's holdings are available. In practice, the -// helper shold rekey it back, but we don't bother here. +// helper should rekey it back, but we don't bother here. func TestInnerAppCreateAndOptin(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -3163,7 +3163,7 @@ app_local_put var problem string switch { case ver < 34: // before v7, app accounts not available at all - problem = "invalid Account reference " + id0.Address().String() + problem = "unavailable Account " + id0.Address().String() case ver < 38: // as of v7, it's the mutation that's the problem problem = "invalid Account reference for mutation" } diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index 2cbec642ab..93f228d596 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -128,6 +128,7 @@ var passThruSource = main(` `) const boxVersion = 36 +const boxQuotaBumpVersion = 41 func boxFee(p config.ConsensusParams, nameAndValueSize uint64) uint64 { return p.BoxFlatMinBalance + p.BoxByteMinBalance*(nameAndValueSize) @@ -539,16 +540,20 @@ func TestBoxIOBudgets(t *testing.T) { ApplicationID: appID, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("x")}}, } - dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 - "write budget (1024) exceeded") - call.Boxes = append(call.Boxes, transactions.BoxRef{}) + if ver < boxQuotaBumpVersion { + dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 + "write budget (1024) exceeded") + call.Boxes = append(call.Boxes, transactions.BoxRef{}) + } dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 "write budget (2048) exceeded") call.Boxes = append(call.Boxes, transactions.BoxRef{}) - dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 - "write budget (3072) exceeded") - call.Boxes = append(call.Boxes, transactions.BoxRef{}) - dl.txn(call.Args("create", "x", "\x10\x00"), // now there are 4 box refs + if ver < boxQuotaBumpVersion { + dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 + "write budget (3072) exceeded") + call.Boxes = append(call.Boxes, transactions.BoxRef{}) + } + dl.txn(call.Args("create", "x", "\x10\x00"), // now there are enough box refs "below min") // big box would need more balance dl.txn(call.Args("create", "x", "\x10\x01"), // 4097 "write budget (4096) exceeded") @@ -572,11 +577,10 @@ func TestBoxIOBudgets(t *testing.T) { dl.txgroup("", &fundApp, create) // Now that we've created a 4,096 byte box, test READ budget - // It works at the start, because call still has 4 brs. + // It works at the start, because call still has enough brs. dl.txn(call.Args("check", "x", "\x00")) - call.Boxes = call.Boxes[:3] - dl.txn(call.Args("check", "x", "\x00"), - "box read budget (3072) exceeded") + call.Boxes = call.Boxes[:len(call.Boxes)-1] // remove one ref + dl.txn(call.Args("check", "x", "\x00"), "box read budget") // Give a budget over 32768, confirm failure anyway empties := [32]transactions.BoxRef{} @@ -603,7 +607,7 @@ func TestBoxInners(t *testing.T) { dl.txn(&txntest.Txn{Type: "pay", Sender: addrs[0], Receiver: addrs[0]}) dl.txn(&txntest.Txn{Type: "pay", Sender: addrs[0], Receiver: addrs[0]}) - boxID := dl.fundedApp(addrs[0], 2_000_000, boxAppSource) // there are some big boxes made + boxID := dl.fundedApp(addrs[0], 4_000_000, boxAppSource) // there are some big boxes made passID := dl.fundedApp(addrs[0], 120_000, passThruSource) // lowish, show it's not paying for boxes call := txntest.Txn{ Type: "appl", @@ -621,11 +625,19 @@ func TestBoxInners(t *testing.T) { require.Error(t, call.Txn().WellFormed(transactions.SpecialAddresses{}, dl.generator.genesisProto)) call.Boxes = []transactions.BoxRef{{Index: 1, Name: []byte("x")}} - dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 - "write budget (1024) exceeded") - dl.txn(call.Args("create", "x", "\x04\x00")) // 1024 - call.Boxes = append(call.Boxes, transactions.BoxRef{Index: 1, Name: []byte("y")}) - dl.txn(call.Args("create", "y", "\x08\x00")) // 2048 + if ver < boxQuotaBumpVersion { + dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 + "write budget (1024) exceeded") + dl.txn(call.Args("create", "x", "\x04\x00")) // 1024 + call.Boxes = append(call.Boxes, transactions.BoxRef{Index: 1, Name: []byte("y")}) + dl.txn(call.Args("create", "y", "\x08\x00")) // 2048 + } else { + dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 + "write budget (2048) exceeded") + dl.txn(call.Args("create", "x", "\x08\x00")) // 2048 + call.Boxes = append(call.Boxes, transactions.BoxRef{Index: 1, Name: []byte("y")}) + dl.txn(call.Args("create", "y", "\x10\x00")) // 4096 + } require.Len(t, call.Boxes, 2) setX := call.Args("set", "x", "A") diff --git a/ledger/simulation/resources.go b/ledger/simulation/resources.go index 2132d20710..ae255c5e8d 100644 --- a/ledger/simulation/resources.go +++ b/ledger/simulation/resources.go @@ -42,7 +42,7 @@ type ResourceTracker struct { // The map value is the size of the box loaded from the ledger prior to any writes. This is used // to track the box read budget. - Boxes map[logic.BoxRef]uint64 + Boxes map[basics.BoxRef]uint64 MaxBoxes int NumEmptyBoxRefs int maxWriteBudget uint64 @@ -224,7 +224,7 @@ func (a *ResourceTracker) removeAppSlot() bool { func (a *ResourceTracker) hasBox(app basics.AppIndex, name string) bool { // nil map lookup is ok - _, ok := a.Boxes[logic.BoxRef{App: app, Name: name}] + _, ok := a.Boxes[basics.BoxRef{App: app, Name: name}] return ok } @@ -257,9 +257,9 @@ func (a *ResourceTracker) addBox(app basics.AppIndex, name string, readSize, add return false } if a.Boxes == nil { - a.Boxes = make(map[logic.BoxRef]uint64) + a.Boxes = make(map[basics.BoxRef]uint64) } - a.Boxes[logic.BoxRef{App: app, Name: name}] = readSize + a.Boxes[basics.BoxRef{App: app, Name: name}] = readSize a.NumEmptyBoxRefs += emptyRefs return true } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index acd3ad232a..936a3047c4 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -7067,7 +7067,7 @@ func TestUnnamedResources(t *testing.T) { if v >= 8 { // boxes introduced program += `byte "A"; int 64; box_create; assert;` program += `byte "B"; box_len; !; assert; !; assert;` - expectedUnnamedResourceGroupAssignment.Boxes = map[logic.BoxRef]uint64{ + expectedUnnamedResourceGroupAssignment.Boxes = map[basics.BoxRef]uint64{ {App: 0, Name: "A"}: 0, {App: 0, Name: "B"}: 0, } @@ -7107,7 +7107,7 @@ func TestUnnamedResources(t *testing.T) { local.Address = testAppID.Address() expectedUnnamedResourceGroupAssignment.AppLocals[local] = struct{}{} } - var boxesToFix []logic.BoxRef + var boxesToFix []basics.BoxRef for box := range expectedUnnamedResourceGroupAssignment.Boxes { if box.App == 0 { // replace with app ID @@ -7602,7 +7602,7 @@ func (o boxOperation) boxRefs() []transactions.BoxRef { } type boxTestResult struct { - Boxes map[logic.BoxRef]uint64 + Boxes map[basics.BoxRef]uint64 NumEmptyBoxRefs int FailureMessage string @@ -7780,21 +7780,21 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "A"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, }) testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "B"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "B"}: 1, }, }) testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "C"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, }, // We need an additional empty box ref because the size of C exceeds BytesPerBoxReference @@ -7804,7 +7804,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxReadOperation, name: "A"}, {op: logic.BoxReadOperation, name: "B"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "A"}: proto.BytesPerBoxReference, {App: appID, Name: "B"}: 1, }, @@ -7813,7 +7813,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxReadOperation, name: "A"}, {op: logic.BoxReadOperation, name: "C"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "A"}: proto.BytesPerBoxReference, {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, }, @@ -7824,7 +7824,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxReadOperation, name: "B"}, {op: logic.BoxReadOperation, name: "C"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "A"}: proto.BytesPerBoxReference, {App: appID, Name: "B"}: 1, {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, @@ -7834,7 +7834,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "Q"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "Q"}: 0, }, }) @@ -7843,14 +7843,14 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { testBoxOps([]boxOperation{ {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, }, }) testBoxOps([]boxOperation{ {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 1}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, }, NumEmptyBoxRefs: 1, @@ -7858,7 +7858,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { testBoxOps([]boxOperation{ {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference * 3}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, }, NumEmptyBoxRefs: 2, @@ -7867,7 +7867,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxCreateOperation, name: "D", createSize: 1}, {op: logic.BoxCreateOperation, name: "E", createSize: 1}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, {App: appID, Name: "E"}: 0, }, @@ -7878,7 +7878,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, {op: logic.BoxReadOperation, name: "A"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, @@ -7889,7 +7889,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxReadOperation, name: "A"}, {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, @@ -7901,7 +7901,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, {op: logic.BoxWriteOperation, name: "A", contents: []byte{1}}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, @@ -7911,7 +7911,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, {op: logic.BoxWriteOperation, name: "B", contents: []byte{1}}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, {App: appID, Name: "B"}: 1, }, @@ -7924,7 +7924,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxCreateOperation, name: "D", createSize: 4 * proto.BytesPerBoxReference}, {op: logic.BoxDeleteOperation, name: "D"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, }, // Still need 3 empty box refs because we went over the write budget before deletion. @@ -7937,7 +7937,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { {op: logic.BoxDeleteOperation, name: "D"}, {op: logic.BoxReadOperation, name: "C"}, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "D"}: 0, {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, }, @@ -7956,7 +7956,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { otherRefCount: proto.MaxAppBoxReferences - 1, }, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, }) @@ -7987,7 +7987,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { otherRefCount: proto.MaxAppBoxReferences, }, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, {App: appID, Name: "X"}: 0, }, @@ -8002,7 +8002,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { otherRefCount: proto.MaxAppBoxReferences - 1, }, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "X"}: 0, }, }) @@ -8016,7 +8016,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { otherRefCount: proto.MaxAppBoxReferences - 1, }, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "X"}: 0, }, FailureMessage: fmt.Sprintf("logic eval error: write budget (%d) exceeded %d", proto.BytesPerBoxReference, proto.BytesPerBoxReference+1), @@ -8037,7 +8037,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { otherRefCount: proto.MaxAppBoxReferences, }, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "X"}: 0, {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, @@ -8056,7 +8056,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { otherRefCount: proto.MaxAppBoxReferences, }, }, boxTestResult{ - Boxes: map[logic.BoxRef]uint64{ + Boxes: map[basics.BoxRef]uint64{ {App: appID, Name: "A"}: proto.BytesPerBoxReference, }, FailureMessage: fmt.Sprintf("logic eval error: invalid Box reference %#x", "B"), @@ -8440,14 +8440,14 @@ func mapWithKeys[K comparable, V any](keys []K, defaultValue V) map[K]V { return m } -func boxNamesToRefs(app basics.AppIndex, names []string) []logic.BoxRef { +func boxNamesToRefs(app basics.AppIndex, names []string) []basics.BoxRef { if names == nil { return nil } - refs := make([]logic.BoxRef, len(names)) + refs := make([]basics.BoxRef, len(names)) for i, name := range names { - refs[i] = logic.BoxRef{ + refs[i] = basics.BoxRef{ App: app, Name: name, } @@ -8660,7 +8660,7 @@ func TestUnnamedResourcesLimits(t *testing.T) { unnamedResourceArguments{}. addAccounts(otherAccounts[:proto.MaxAppTotalTxnReferences+1]...). markLimitExceeded(), - fmt.Sprintf("logic eval error: invalid Account reference %s", otherAccounts[proto.MaxAppTotalTxnReferences]), + fmt.Sprintf("logic eval error: unavailable Account %s", otherAccounts[proto.MaxAppTotalTxnReferences]), ) // Exactly at asset limit @@ -8726,7 +8726,7 @@ func TestUnnamedResourcesLimits(t *testing.T) { // Adding 1 more of any is over the limit testResourceAccess( atLimit.addAccounts(otherAccounts[len(otherAccounts)-1]).markLimitExceeded(), - fmt.Sprintf("logic eval error: invalid Account reference %s", otherAccounts[len(otherAccounts)-1]), + fmt.Sprintf("logic eval error: unavailable Account %s", otherAccounts[len(otherAccounts)-1]), ) testResourceAccess( atLimit.addAssets(assets[len(assets)-1]).markLimitExceeded(), @@ -8877,7 +8877,7 @@ func TestUnnamedResourcesCrossProductLimits(t *testing.T) { atAssetHoldingLimit. addAssetHoldings(assets[assetHoldingLimitIndex], otherAccounts[0]). markLimitExceeded(), - fmt.Sprintf("logic eval error: unavailable Holding %s x %d", otherAccounts[0], assets[assetHoldingLimitIndex]), + fmt.Sprintf("logic eval error: unavailable Holding %d+%s", assets[assetHoldingLimitIndex], otherAccounts[0]), ) // Over app local limit @@ -8885,7 +8885,7 @@ func TestUnnamedResourcesCrossProductLimits(t *testing.T) { atAppLocalLimit. addAppLocals(appID, otherAccounts[0]). markLimitExceeded(), - fmt.Sprintf("logic eval error: unavailable Local State %s x %d", otherAccounts[0], appID), + fmt.Sprintf("logic eval error: unavailable Local State %d+%s", appID, otherAccounts[0]), ) // Over total cross-product limit with asset holding @@ -8893,7 +8893,7 @@ func TestUnnamedResourcesCrossProductLimits(t *testing.T) { atCombinedLimit. addAssetHoldings(assets[1], otherAccounts[0]). markLimitExceeded(), - fmt.Sprintf("logic eval error: unavailable Holding %s x %d", otherAccounts[0], assets[1]), + fmt.Sprintf("logic eval error: unavailable Holding %d+%s", assets[1], otherAccounts[0]), ) // Over total cross-product limit with app local @@ -8901,7 +8901,7 @@ func TestUnnamedResourcesCrossProductLimits(t *testing.T) { atCombinedLimit. addAppLocals(appID, otherAccounts[0]). markLimitExceeded(), - fmt.Sprintf("logic eval error: unavailable Local State %s x %d", otherAccounts[0], appID), + fmt.Sprintf("logic eval error: unavailable Local State %d+%s", appID, otherAccounts[0]), ) }) } diff --git a/libgoal/libgoal_test.go b/libgoal/libgoal_test.go index e2795ec998..eb4fb4f766 100644 --- a/libgoal/libgoal_test.go +++ b/libgoal/libgoal_test.go @@ -17,10 +17,14 @@ package libgoal import ( + "fmt" "testing" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -142,3 +146,237 @@ func TestValidRounds(t *testing.T) { a.EqualValues(100, fv) a.EqualValues(maxTxnLife, lv) } + +// bbrs just saves typing +func bbrs(args ...any) []basics.BoxRef { + if len(args)%2 != 0 { + panic(fmt.Sprintf("odd number of args %v", args)) + } + var refs []basics.BoxRef + for i := 0; i < len(args); i += 2 { + app := basics.AppIndex(args[i].(int)) + name := args[i+1].(string) + refs = append(refs, basics.BoxRef{ + App: app, + Name: name, + }) + } + return refs +} + +func TestForeignResolution(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := assert.New(t) + + tx := txntest.Txn{ + ApplicationID: 111, + }.Txn() + + accounts := []basics.Address{{0x22}, {0x33}} + foreignApps := []basics.AppIndex{222, 333} + foreignAssets := []basics.AssetIndex{2222, 3333} + + attachForeignRefs(&tx, RefBundle{Accounts: accounts}) + a.Equal(accounts, tx.Accounts) + + attachForeignRefs(&tx, RefBundle{Assets: foreignAssets}) + a.Equal(foreignAssets, tx.ForeignAssets) + + attachForeignRefs(&tx, RefBundle{Apps: foreignApps}) + a.Equal(foreignApps, tx.ForeignApps) + + attachForeignRefs(&tx, RefBundle{Apps: foreignApps}) + a.Equal(append(foreignApps, foreignApps...), tx.ForeignApps) + + boxes := bbrs(3, "aaa") + attachForeignRefs(&tx, RefBundle{Boxes: boxes}) + a.Equal([]basics.AppIndex{222, 333, 222, 333, 3}, tx.ForeignApps) + a.Equal([]transactions.BoxRef{{Index: 5, Name: []byte("aaa")}}, tx.Boxes) + + boxes = bbrs(3, "aaa", 0, "bbb") + tx.Boxes = nil + attachForeignRefs(&tx, RefBundle{Boxes: boxes}) + a.Equal([]basics.AppIndex{222, 333, 222, 333, 3}, tx.ForeignApps) + a.Equal([]transactions.BoxRef{ + {Index: 5, Name: []byte("aaa")}, + {Index: 0, Name: []byte("bbb")}, + }, tx.Boxes) + + boxes = bbrs(3, "aaa", 3, "xxx") + attachForeignRefs(&tx, RefBundle{Boxes: boxes}) + a.Equal([]basics.AppIndex{222, 333, 222, 333, 3}, tx.ForeignApps) + a.Equal([]transactions.BoxRef{ + {Index: 5, Name: []byte("aaa")}, + {Index: 0, Name: []byte("bbb")}, + {Index: 5, Name: []byte("aaa")}, + {Index: 5, Name: []byte("xxx")}, + }, tx.Boxes) + + boxes = bbrs(111, "aaa", 333, "xxx") + attachForeignRefs(&tx, RefBundle{Boxes: boxes}) + a.Equal([]basics.AppIndex{222, 333, 222, 333, 3}, tx.ForeignApps) + a.Equal([]transactions.BoxRef{ + {Index: 5, Name: []byte("aaa")}, + {Index: 0, Name: []byte("bbb")}, + {Index: 5, Name: []byte("aaa")}, + {Index: 5, Name: []byte("xxx")}, + {Index: 0, Name: []byte("aaa")}, + {Index: 2, Name: []byte("xxx")}, + }, tx.Boxes) + + zero := basics.Address{0x00} + one := basics.Address{0x01} + two := basics.Address{0x02} + holdings := []basics.HoldingRef{{Asset: 111, Address: one}, {Asset: 3333, Address: zero}} + attachForeignRefs(&tx, RefBundle{Holdings: holdings}) + a.Equal([]basics.AssetIndex{2222, 3333, 111}, tx.ForeignAssets) // it's added, 111 is the APP id + a.Equal(append(accounts, one), tx.Accounts) + + locals := []basics.LocalRef{{App: 111, Address: two}, {App: 333, Address: zero}, {App: 444, Address: one}} + attachForeignRefs(&tx, RefBundle{Locals: locals}) + a.Equal([]basics.AppIndex{222, 333, 222, 333, 3, 444}, tx.ForeignApps) // 111 not added, it's being called + a.Equal(append(accounts, one, two), tx.Accounts) +} + +func TestAccessResolution(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := assert.New(t) + + tx := txntest.Txn{ + ApplicationID: 111, + }.Txn() + + accounts := []basics.Address{{0x22}, {0x33}} + foreignApps := []basics.AppIndex{222, 333} + foreignAssets := []basics.AssetIndex{2222, 3333} + + attachAccessList(&tx, RefBundle{Accounts: accounts}) + a.Nil(tx.Accounts) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + }, tx.Access) + + attachAccessList(&tx, RefBundle{Assets: foreignAssets}) + a.Nil(tx.ForeignAssets) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + }, tx.Access) + + attachAccessList(&tx, RefBundle{Apps: foreignApps}) + a.Nil(tx.ForeignApps) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + }, tx.Access) + + attachAccessList(&tx, RefBundle{Apps: foreignApps}) + // no change + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + }, tx.Access) + + boxes := bbrs(3, "aaa") + attachAccessList(&tx, RefBundle{Boxes: boxes}) + a.Nil(tx.Boxes) + a.Nil(tx.ForeignApps) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + {App: 3}, {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + }, tx.Access) + + boxes = bbrs(3, "aaa", 0, "bbb") + attachAccessList(&tx, RefBundle{Boxes: boxes}) + a.Nil(tx.Boxes) + a.Nil(tx.ForeignApps) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + {App: 3}, {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("bbb")}}, + }, tx.Access) + + boxes = bbrs(3, "aaa", 3, "xxx") + attachAccessList(&tx, RefBundle{Boxes: boxes}) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + {App: 3}, {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("bbb")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("xxx")}}, + }, tx.Access) + + boxes = bbrs(111, "aaa", 333, "xxx") + attachAccessList(&tx, RefBundle{Boxes: boxes}) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + {App: 3}, {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("bbb")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("xxx")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 6, Name: []byte("xxx")}}, + }, tx.Access) + + zero := basics.Address{0x00} + one := basics.Address{0x01} + two := basics.Address{0x02} + holdings := []basics.HoldingRef{{Asset: 111, Address: one}, {Asset: 3333, Address: zero}} + attachAccessList(&tx, RefBundle{Holdings: holdings}) + a.Nil(tx.ForeignAssets) + a.Nil(tx.Accounts) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + {App: 3}, {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("bbb")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("xxx")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 6, Name: []byte("xxx")}}, + {Address: one}, {Asset: 111}, {Holding: transactions.HoldingRef{Asset: 16, Address: 15}}, + {Holding: transactions.HoldingRef{Asset: 4, Address: 0}}, + }, tx.Access) + + locals := []basics.LocalRef{{App: 111, Address: two}, {App: 333, Address: zero}, {App: 444, Address: one}} + attachAccessList(&tx, RefBundle{Locals: locals}) + a.Nil(tx.ForeignApps) + a.Nil(tx.Accounts) + a.Equal([]transactions.ResourceRef{ + {Address: accounts[0]}, {Address: accounts[1]}, + {Asset: foreignAssets[0]}, {Asset: foreignAssets[1]}, + {App: foreignApps[0]}, {App: foreignApps[1]}, + {App: 3}, {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("bbb")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 7, Name: []byte("xxx")}}, + {Box: transactions.BoxRef{Index: 0, Name: []byte("aaa")}}, + {Box: transactions.BoxRef{Index: 6, Name: []byte("xxx")}}, + {Address: one}, {Asset: 111}, {Holding: transactions.HoldingRef{Asset: 16, Address: 15}}, + {Holding: transactions.HoldingRef{Asset: 4, Address: 0}}, + + {Address: two}, + {Locals: transactions.LocalsRef{App: 0, Address: 19}}, + {Locals: transactions.LocalsRef{App: 6, Address: 0}}, + {App: 444}, + {Locals: transactions.LocalsRef{App: 22, Address: 15}}, + }, tx.Access) +} diff --git a/libgoal/transactions.go b/libgoal/transactions.go index 14196e78c2..db3595396b 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -19,6 +19,7 @@ package libgoal import ( "errors" "fmt" + "slices" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -517,65 +518,74 @@ func (c *Client) FillUnsignedTxTemplate(sender string, firstValid, lastValid bas return tx, nil } +// RefBundle holds all of the "foreign" references an app call needs, and +// handles converting to the proper form in the transaction. Depending on +// UseAccess, it can pack the references in the tx.Access list, or use the older +// foreign arrays. +type RefBundle struct { + UseAccess bool + + Accounts []basics.Address + Assets []basics.AssetIndex + Holdings []basics.HoldingRef + Apps []basics.AppIndex + Locals []basics.LocalRef + Boxes []basics.BoxRef +} + // MakeUnsignedAppCreateTx makes a transaction for creating an application -func (c *Client) MakeUnsignedAppCreateTx(onComplete transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, extrapages uint32) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(0, appArgs, accounts, foreignApps, foreignAssets, boxes, onComplete, approvalProg, clearProg, globalSchema, localSchema, extrapages, 0) +func (c *Client) MakeUnsignedAppCreateTx(onComplete transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, appArgs [][]byte, refs RefBundle, extrapages uint32) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(0, appArgs, refs, onComplete, approvalProg, clearProg, globalSchema, localSchema, extrapages, 0) } // MakeUnsignedAppUpdateTx makes a transaction for updating an application's programs -func (c *Client) MakeUnsignedAppUpdateTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, approvalProg []byte, clearProg []byte, rejectVersion uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.UpdateApplicationOC, approvalProg, clearProg, emptySchema, emptySchema, 0, rejectVersion) +func (c *Client) MakeUnsignedAppUpdateTx(appIdx basics.AppIndex, appArgs [][]byte, approvalProg []byte, clearProg []byte, refs RefBundle, rejectVersion uint64) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, transactions.UpdateApplicationOC, approvalProg, clearProg, emptySchema, emptySchema, 0, rejectVersion) } // MakeUnsignedAppDeleteTx makes a transaction for deleting an application -func (c *Client) MakeUnsignedAppDeleteTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.DeleteApplicationOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) +func (c *Client) MakeUnsignedAppDeleteTx(appIdx basics.AppIndex, appArgs [][]byte, refs RefBundle, rejectVersion uint64) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, transactions.DeleteApplicationOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) } // MakeUnsignedAppOptInTx makes a transaction for opting in to (allocating // some account-specific state for) an application -func (c *Client) MakeUnsignedAppOptInTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.OptInOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) +func (c *Client) MakeUnsignedAppOptInTx(appIdx basics.AppIndex, appArgs [][]byte, refs RefBundle, rejectVersion uint64) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, transactions.OptInOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) } // MakeUnsignedAppCloseOutTx makes a transaction for closing out of // (deallocating all account-specific state for) an application -func (c *Client) MakeUnsignedAppCloseOutTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.CloseOutOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) +func (c *Client) MakeUnsignedAppCloseOutTx(appIdx basics.AppIndex, appArgs [][]byte, refs RefBundle, rejectVersion uint64) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, transactions.CloseOutOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) } // MakeUnsignedAppClearStateTx makes a transaction for clearing out all // account-specific state for an application. It may not be rejected by the // application's logic. -func (c *Client) MakeUnsignedAppClearStateTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.ClearStateOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) +func (c *Client) MakeUnsignedAppClearStateTx(appIdx basics.AppIndex, appArgs [][]byte, refs RefBundle, rejectVersion uint64) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, transactions.ClearStateOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) } // MakeUnsignedAppNoOpTx makes a transaction for interacting with an existing // application, potentially updating any account-specific local state and // global state associated with it. -func (c *Client) MakeUnsignedAppNoOpTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.NoOpOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) +func (c *Client) MakeUnsignedAppNoOpTx(appIdx basics.AppIndex, appArgs [][]byte, refs RefBundle, rejectVersion uint64) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, refs, transactions.NoOpOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion) } // MakeUnsignedApplicationCallTx is a helper for the above ApplicationCall // transaction constructors. A fully custom ApplicationCall transaction may // be constructed using this method. -func (c *Client) MakeUnsignedApplicationCallTx(appIdx basics.AppIndex, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, onCompletion transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, extrapages uint32, rejectVersion uint64) (tx transactions.Transaction, err error) { +func (c *Client) MakeUnsignedApplicationCallTx(callee basics.AppIndex, appArgs [][]byte, refs RefBundle, onCompletion transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, extrapages uint32, rejectVersion uint64) (tx transactions.Transaction, err error) { tx.Type = protocol.ApplicationCallTx - tx.ApplicationID = appIdx + tx.ApplicationID = callee tx.OnCompletion = onCompletion tx.RejectVersion = rejectVersion tx.ApplicationArgs = appArgs - tx.Accounts, err = parseTxnAccounts(accounts) - if err != nil { - return tx, err - } - tx.ForeignApps = parseTxnForeignApps(foreignApps) - tx.ForeignAssets = parseTxnForeignAssets(foreignAssets) - tx.Boxes = boxes + attachReferences(&tx, refs) tx.ApprovalProgram = approvalProg tx.ClearStateProgram = clearProg tx.LocalStateSchema = localSchema @@ -585,29 +595,134 @@ func (c *Client) MakeUnsignedApplicationCallTx(appIdx basics.AppIndex, appArgs [ return tx, nil } -func parseTxnAccounts(accounts []string) (parsed []basics.Address, err error) { - for _, acct := range accounts { - addr, err := basics.UnmarshalChecksumAddress(acct) - if err != nil { - return nil, err +// attachReferences adds the foreign arrays or access list required to access +// the resources in the RefBundle. +func attachReferences(tx *transactions.Transaction, refs RefBundle) { + if refs.UseAccess { + attachAccessList(tx, refs) + } else { + attachForeignRefs(tx, refs) + } +} + +// attachAccessList populates the transaction with the new style access list. +func attachAccessList(tx *transactions.Transaction, refs RefBundle) { + // ensure looks for a "simple" resource ref that is needed by a cross-product + // ref. If found, return the 1-based index. If not found, insert and return + // its (new) index. + ensure := func(target transactions.ResourceRef) uint64 { + // We always check all three, though calls will only have one set. Less code duplication. + idx := slices.IndexFunc(tx.Access, func(present transactions.ResourceRef) bool { + return present.Address == target.Address && + present.Asset == target.Asset && + present.App == target.App + }) + if idx != -1 { + return uint64(idx) + 1 } - parsed = append(parsed, addr) + tx.Access = append(tx.Access, target) + return uint64(len(tx.Access)) } - return -} -func parseTxnForeignApps(foreignApps []uint64) (parsed []basics.AppIndex) { - for _, aidx := range foreignApps { - parsed = append(parsed, basics.AppIndex(aidx)) + for _, addr := range refs.Accounts { + ensure(transactions.ResourceRef{Address: addr}) + } + for _, asset := range refs.Assets { + ensure(transactions.ResourceRef{Asset: asset}) + } + for _, app := range refs.Apps { + ensure(transactions.ResourceRef{App: app}) + } + + for _, hr := range refs.Holdings { + addrIdx := uint64(0) + if !hr.Address.IsZero() { + addrIdx = ensure(transactions.ResourceRef{Address: hr.Address}) + } + tx.Access = append(tx.Access, transactions.ResourceRef{Holding: transactions.HoldingRef{ + Asset: ensure(transactions.ResourceRef{Asset: hr.Asset}), + Address: addrIdx, + }}) } - return -} -func parseTxnForeignAssets(foreignAssets []uint64) (parsed []basics.AssetIndex) { - for _, aidx := range foreignAssets { - parsed = append(parsed, basics.AssetIndex(aidx)) + for _, lr := range refs.Locals { + appIdx := uint64(0) + if lr.App != 0 && lr.App != tx.ApplicationID { + appIdx = ensure(transactions.ResourceRef{App: lr.App}) + } + addrIdx := uint64(0) + if !lr.Address.IsZero() { + addrIdx = ensure(transactions.ResourceRef{Address: lr.Address}) + } + tx.Access = append(tx.Access, transactions.ResourceRef{Locals: transactions.LocalsRef{ + App: appIdx, + Address: addrIdx, + }}) + } + + for _, br := range refs.Boxes { + appIdx := uint64(0) + if br.App != 0 && br.App != tx.ApplicationID { + appIdx = ensure(transactions.ResourceRef{App: br.App}) + } + tx.Access = append(tx.Access, transactions.ResourceRef{Box: transactions.BoxRef{ + Index: appIdx, + Name: []byte(br.Name), + }}) + } +} + +// maybeAppend looks for something in a slice. If found, it returns its index. If +// not found, append and return the (new) index. +func maybeAppend[S ~[]E, E comparable](slice S, target E) (S, int) { + idx := slices.Index(slice, target) + if idx != -1 { + return slice, idx + } + slice = append(slice, target) + return slice, len(slice) - 1 +} + +func attachForeignRefs(tx *transactions.Transaction, refs RefBundle) { + // We must add these as given, (not dedupe) + tx.Accounts = append(tx.Accounts, refs.Accounts...) + tx.ForeignAssets = append(tx.ForeignAssets, refs.Assets...) + tx.ForeignApps = append(tx.ForeignApps, refs.Apps...) + + // add assets, addresses if Holdings need them + for _, hr := range refs.Holdings { + tx.ForeignAssets, _ = maybeAppend(tx.ForeignAssets, hr.Asset) + if !hr.Address.IsZero() && // Zero address used to convey "Sender" + !slices.ContainsFunc(tx.ForeignApps, func(id basics.AppIndex) bool { + return id.Address() == hr.Address + }) { + tx.Accounts, _ = maybeAppend(tx.Accounts, hr.Address) + } + } + // add apps, addresses if Locals need them + for _, lr := range refs.Locals { + if lr.App != 0 && lr.App != tx.ApplicationID { + tx.ForeignApps, _ = maybeAppend(tx.ForeignApps, lr.App) + } + if !lr.Address.IsZero() && // Zero address used to convey "Sender" + !slices.ContainsFunc(tx.ForeignApps, func(id basics.AppIndex) bool { + return id.Address() == lr.Address + }) { + tx.Accounts, _ = maybeAppend(tx.Accounts, lr.Address) + } + } + // add boxes (and their app, if needed) + for _, br := range refs.Boxes { + index := 0 + if br.App != 0 && br.App != tx.ApplicationID { + tx.ForeignApps, index = maybeAppend(tx.ForeignApps, br.App) + index++ // 1-based index + } + tx.Boxes = append(tx.Boxes, transactions.BoxRef{ + Index: uint64(index), + Name: []byte(br.Name), + }) } - return } // MakeUnsignedAssetCreateTx creates a tx template for creating diff --git a/scripts/export_sdk_types.py b/scripts/export_sdk_types.py index a95d742ada..b74e9b9410 100755 --- a/scripts/export_sdk_types.py +++ b/scripts/export_sdk_types.py @@ -108,8 +108,9 @@ def sdkize(input): input = input.replace("OneTimeSignatureVerifier", "VotePK") input = input.replace("VRFVerifier", "VRFPK") input = input.replace("merklesignature.Commitment", "MerkleVerifier") - # appl - Someone had the bright idea to change the name of this field (and type) in the SDK. + # appl - Someone had the bright idea to change the names of these fields (and the type) in the SDK. input = input.replace("Boxes []BoxRef", "BoxReferences []BoxReference") + input = re.sub("Box\\s+BoxRef", "Box BoxReference", input) # transaction - for some reason, ApplicationCallTxnFields is wrapped in this nothing-burger input = input.replace("ApplicationCallTxnFields", "ApplicationFields") @@ -141,14 +142,23 @@ def export_thing(pattern, name, src, dst): line = find_line(src, start) if line == "": raise ValueError(f"Unable to find {name} in {src}") - stop = "\n}\n" if line.endswith("{") else "\n" - x = extract_between(src, start, stop) + src_stop = "\n}\n" if line.endswith("{") else "\n" + x = extract_between(src, start, src_stop) x = sdkize(x) if dst.endswith(".go"): # explicit dst dst = f"{SDK}{dst}" else: dst = f"{SDK}types/{dst}.go" - replace_between(dst, x, start, stop) + + line = find_line(dst, start) + if line == "": + raise ValueError(f"Unable to find {name} in {dst} If it's new, add a dummy version to place it.") + dst_stop = "\n}\n" if line.endswith("{") else "\n" + # Allow a struct to replace a one-line type def by adding } to extracted text + if "}" in src_stop and "}" not in dst_stop: + x += "\n}" + replace_between(dst, x, start, dst_stop) + subprocess.run(["gofmt", "-w", dst]) if __name__ == "__main__": @@ -186,6 +196,11 @@ def export_thing(pattern, name, src, dst): export_type("AssetParams", "data/basics/userBalance.go", "asset") # apps export_type("ApplicationCallTxnFields", "data/transactions/application.go", "applications") + export_type("ResourceRef", "data/transactions/application.go", "applications") + # Don't export this, since it was greatly modified in the SDK. We'll just stick to the manual definition. + # export_type("BoxRef", "data/transactions/application.go", "applications") + export_type("HoldingRef", "data/transactions/application.go", "applications") + export_type("LocalsRef", "data/transactions/application.go", "applications") export_type("AppIndex", "data/basics/userBalance.go", "applications") # Block diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go index 8af1f9bff7..3b86e868a3 100644 --- a/shared/pingpong/accounts.go +++ b/shared/pingpong/accounts.go @@ -870,7 +870,7 @@ func (pps *WorkerState) newApp(addr string, client *libgoal.Client) (tx transact globSchema := basics.StateSchema{NumByteSlice: proto.MaxGlobalSchemaEntries} locSchema := basics.StateSchema{NumByteSlice: proto.MaxLocalSchemaEntries} - tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, prog, prog, globSchema, locSchema, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, prog, prog, globSchema, locSchema, nil, libgoal.RefBundle{}, 0) if err != nil { fmt.Printf("Cannot create app txn\n") panic(err) @@ -891,7 +891,7 @@ func (pps *WorkerState) newApp(addr string, client *libgoal.Client) (tx transact } func (pps *WorkerState) appOptIn(addr string, appID basics.AppIndex, client *libgoal.Client) (tx transactions.Transaction, err error) { - tx, err = client.MakeUnsignedAppOptInTx(appID, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppOptInTx(appID, nil, libgoal.RefBundle{}, 0) if err != nil { fmt.Printf("Cannot create app txn\n") panic(err) diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go index bfcff07deb..99942fd7eb 100644 --- a/shared/pingpong/pingpong.go +++ b/shared/pingpong/pingpong.go @@ -30,6 +30,7 @@ import ( "math" "math/rand" "os" + "slices" "strings" "sync/atomic" "time" @@ -1198,13 +1199,12 @@ func (pps *WorkerState) constructAppTxn(from string, fee uint64, client *libgoal } // construct box ref array - var boxRefs []transactions.BoxRef + var boxRefs []basics.BoxRef for i := range pps.getNumBoxes() { - boxRefs = append(boxRefs, transactions.BoxRef{Index: 0, Name: []byte{fmt.Sprintf("%d", i)[0]}}) + boxRefs = append(boxRefs, basics.BoxRef{App: 0, Name: fmt.Sprintf("%d", i)}) } appOptIns := pps.cinfo.OptIns[aidx] - sender = from if len(appOptIns) > 0 { indices := rand.Perm(len(appOptIns)) limit := 5 @@ -1215,24 +1215,27 @@ func (pps *WorkerState) constructAppTxn(from string, fee uint64, client *libgoal idx := indices[i] accounts = append(accounts, appOptIns[idx]) } - if pps.cinfo.AppParams[aidx].Creator == from { - // if the application was created by the "from" account, then we don't need to worry about it being opted-in. - } else { - fromIsOptedIn := false - for i := 0; i < len(appOptIns); i++ { - if appOptIns[i] == from { - fromIsOptedIn = true - break - } - } - if !fromIsOptedIn { - sender = accounts[0] - from = sender - } + // change `from` to an account that's opted-in. creator also allowed. + if pps.cinfo.AppParams[aidx].Creator != from && + !slices.Contains(appOptIns, from) { + from = accounts[0] } accounts = accounts[1:] } - txn, err = client.MakeUnsignedAppNoOpTx(aidx, nil, accounts, nil, nil, boxRefs, 0) + addresses := make([]basics.Address, 0, len(accounts)) + for _, acct := range accounts { + var addr basics.Address + addr, err = basics.UnmarshalChecksumAddress(acct) + if err != nil { + return + } + addresses = append(addresses, addr) + } + refs := libgoal.RefBundle{ + Accounts: addresses, + Boxes: boxRefs, + } + txn, err = client.MakeUnsignedAppNoOpTx(aidx, nil, refs, 0) if err != nil { return } diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go index ea0e69d455..e0a94c9eb3 100644 --- a/test/e2e-go/features/accountPerf/sixMillion_test.go +++ b/test/e2e-go/features/accountPerf/sixMillion_test.go @@ -1154,7 +1154,7 @@ int 1 // create the app appTx, err = client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0) require.NoError(t, err) note := make([]byte, 8) @@ -1181,7 +1181,7 @@ func makeOptInAppTransaction( tLife basics.Round, genesisHash crypto.Digest) (appTx transactions.Transaction) { - appTx, err := client.MakeUnsignedAppOptInTx(appIdx, nil, nil, nil, nil, nil, 0) + appTx, err := client.MakeUnsignedAppOptInTx(appIdx, nil, libgoal.RefBundle{}, 0) require.NoError(t, err) appTx.Header = transactions.Header{ @@ -1287,7 +1287,7 @@ func callAppTransaction( tLife basics.Round, genesisHash crypto.Digest) (appTx transactions.Transaction) { - appTx, err := client.MakeUnsignedAppNoOpTx(appIdx, nil, nil, nil, nil, nil, 0) + appTx, err := client.MakeUnsignedAppNoOpTx(appIdx, nil, libgoal.RefBundle{}, 0) require.NoError(t, err) appTx.Header = transactions.Header{ diff --git a/test/e2e-go/features/transactions/accountv2_test.go b/test/e2e-go/features/transactions/accountv2_test.go index 08bf3335d5..93e17f85e4 100644 --- a/test/e2e-go/features/transactions/accountv2_test.go +++ b/test/e2e-go/features/transactions/accountv2_test.go @@ -160,7 +160,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) @@ -215,7 +215,7 @@ int 1 checkEvalDelta(t, &client, txnRound, txnRound+1, 1, 1) // call the app - tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) @@ -295,7 +295,7 @@ int 1 a.Equal(creator, app.Params.Creator) // call the app - tx, err = client.MakeUnsignedAppNoOpTx(appIdx, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppNoOpTx(appIdx, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) @@ -465,7 +465,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) @@ -520,7 +520,7 @@ int 1 checkEvalDelta(t, &client, txnRound, txnRound+1, 1, 1) // call the app - tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, libgoal.RefBundle{}, 0) a.NoError(err) if foreignAssets != nil { tx.ForeignAssets = foreignAssets @@ -610,7 +610,7 @@ int 1 a.Equal(creator, app.Params.Creator) // call the app - tx, err = client.MakeUnsignedAppNoOpTx(appIdx, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppNoOpTx(appIdx, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go index d9a6b79753..27d791acbc 100644 --- a/test/e2e-go/features/transactions/app_pages_test.go +++ b/test/e2e-go/features/transactions/app_pages_test.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -89,7 +90,7 @@ return // create app 1 with 1 extra page app1ExtraPages := uint32(1) - tx, err := client.MakeUnsignedAppCreateTx(transactions.NoOpOC, smallProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, nil, app1ExtraPages) + tx, err := client.MakeUnsignedAppCreateTx(transactions.NoOpOC, smallProgram, smallProgram, globalSchema, localSchema, nil, libgoal.RefBundle{}, app1ExtraPages) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -110,7 +111,7 @@ return a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages)) // update app 1 and ensure the extra page still works - tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, nil, nil, nil, nil, bigProgram, smallProgram, 0) + tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, bigProgram, smallProgram, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -130,7 +131,7 @@ return // create app 2 with 2 extra pages app2ExtraPages := uint32(2) - tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, bigProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, nil, app2ExtraPages) + tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, bigProgram, smallProgram, globalSchema, localSchema, nil, libgoal.RefBundle{}, app2ExtraPages) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -151,7 +152,7 @@ return a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages+app2ExtraPages)) // delete app 1 - tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -170,7 +171,7 @@ return a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app2ExtraPages)) // delete app 2 - tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go index b9530347e5..a3aa476b61 100644 --- a/test/e2e-go/features/transactions/application_test.go +++ b/test/e2e-go/features/transactions/application_test.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" @@ -85,7 +86,7 @@ log // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go index b0c2accd5f..17454a9de5 100644 --- a/test/e2e-go/restAPI/other/appsRestAPI_test.go +++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" @@ -95,7 +96,7 @@ return lc := basics.StateSchema{} // create app - appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0, 0) + appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, libgoal.RefBundle{}, transactions.NoOpOC, approv, clst, gl, lc, 0, 0) a.NoError(err) appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn) a.NoError(err) @@ -119,7 +120,7 @@ return a.NoError(err) // call app, which will issue an ASA create inner txn - appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(createdAppID, nil, nil, nil, nil, nil, 0) + appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(createdAppID, nil, libgoal.RefBundle{}, 0) a.NoError(err) appCallTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn) a.NoError(err) @@ -231,8 +232,7 @@ end: // create app appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( - 0, nil, nil, nil, - nil, nil, transactions.NoOpOC, + 0, nil, libgoal.RefBundle{}, transactions.NoOpOC, approval, clearState, gl, lc, 0, 0, ) a.NoError(err) @@ -274,15 +274,10 @@ end: []byte(boxNames[i]), []byte(boxValues[i]), } - boxRef := transactions.BoxRef{ - Name: []byte(boxNames[i]), - Index: 0, - } + refs := libgoal.RefBundle{Boxes: []basics.BoxRef{{App: 0, Name: boxNames[i]}}} txns[i], err = testClient.MakeUnsignedAppNoOpTx( - createdAppID, appArgs, - nil, nil, nil, - []transactions.BoxRef{boxRef}, 0, + createdAppID, appArgs, refs, 0, ) a.NoError(err) txns[i], err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, txns[i]) @@ -536,7 +531,7 @@ end: a.Equal(uint64(30), appAccountData.TotalBoxBytes) // delete the app - appDeleteTxn, err := testClient.MakeUnsignedAppDeleteTx(createdAppID, nil, nil, nil, nil, nil, 0) + appDeleteTxn, err := testClient.MakeUnsignedAppDeleteTx(createdAppID, nil, libgoal.RefBundle{}, 0) a.NoError(err) appDeleteTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appDeleteTxn) a.NoError(err) @@ -617,8 +612,7 @@ func TestBlockLogs(t *testing.T) { // create app appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( - 0, nil, nil, nil, - nil, nil, transactions.NoOpOC, + 0, nil, libgoal.RefBundle{}, transactions.NoOpOC, outerApproval, clearState, gl, lc, 0, 0, ) a.NoError(err) @@ -648,8 +642,7 @@ func TestBlockLogs(t *testing.T) { // call app twice appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( - createdAppID, nil, nil, nil, - nil, nil, 0, + createdAppID, nil, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallTxn0, err := testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn) diff --git a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go index 472b6f8ecb..db3ca80be6 100644 --- a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go +++ b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go @@ -34,6 +34,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/simulation" + "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" @@ -263,8 +264,7 @@ int 1` clearState := ops.Program txn, err := testClient.MakeUnsignedApplicationCallTx( - 0, nil, nil, nil, - nil, nil, transactions.NoOpOC, + 0, nil, libgoal.RefBundle{}, transactions.NoOpOC, approval, clearState, basics.StateSchema{}, basics.StateSchema{}, 0, 0, ) a.NoError(err) @@ -471,8 +471,7 @@ int 1` // create app appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( - 0, nil, nil, nil, - nil, nil, transactions.NoOpOC, + 0, nil, libgoal.RefBundle{}, transactions.NoOpOC, approval, clearState, gl, lc, 0, 0, ) a.NoError(err) @@ -501,8 +500,7 @@ int 1` // construct app call appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( - createdAppID, [][]byte{[]byte("first-arg")}, - nil, nil, nil, nil, 0, + createdAppID, [][]byte{[]byte("first-arg")}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn) @@ -599,8 +597,7 @@ int 1` // create app appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( - 0, nil, nil, nil, - nil, nil, transactions.NoOpOC, + 0, nil, libgoal.RefBundle{}, transactions.NoOpOC, approval, clearState, gl, lc, 0, 0, ) a.NoError(err) @@ -629,7 +626,7 @@ int 1` // construct app call appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( - createdAppID, nil, nil, nil, nil, nil, 0, + createdAppID, nil, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn) @@ -868,7 +865,7 @@ func TestMaxDepthAppWithPCandStackTrace(t *testing.T) { // create app and get the application ID appCreateTxn, err := testClient.MakeUnsignedAppCreateTx( transactions.NoOpOC, approval, clearState, gl, - lc, nil, nil, nil, nil, nil, 0) + lc, nil, libgoal.RefBundle{}, 0) a.NoError(err) appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn) a.NoError(err) @@ -894,7 +891,7 @@ func TestMaxDepthAppWithPCandStackTrace(t *testing.T) { // construct app calls appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( - futureAppID, [][]byte{uint64ToBytes(uint64(MaxDepth))}, nil, nil, nil, nil, 0, + futureAppID, [][]byte{uint64ToBytes(uint64(MaxDepth))}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee*uint64(3*MaxDepth+2), appCallTxn) @@ -1716,7 +1713,7 @@ func TestSimulateScratchSlotChange(t *testing.T) { // create app and get the application ID appCreateTxn, err := testClient.MakeUnsignedAppCreateTx( transactions.NoOpOC, approval, clearState, gl, - lc, nil, nil, nil, nil, nil, 0) + lc, nil, libgoal.RefBundle{}, 0) a.NoError(err) appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn) a.NoError(err) @@ -1736,7 +1733,7 @@ func TestSimulateScratchSlotChange(t *testing.T) { // construct app calls appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( - futureAppID, [][]byte{}, nil, nil, nil, nil, 0, + futureAppID, [][]byte{}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallTxn) @@ -1910,7 +1907,7 @@ end: // create app and get the application ID appCreateTxn, err := testClient.MakeUnsignedAppCreateTx( transactions.NoOpOC, approval, clearState, gl, - lc, nil, nil, nil, nil, nil, 0) + lc, nil, libgoal.RefBundle{}, 0) a.NoError(err) appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn) a.NoError(err) @@ -1930,18 +1927,18 @@ end: // construct app call "global" appCallGlobalTxn, err := testClient.MakeUnsignedAppNoOpTx( - futureAppID, [][]byte{[]byte("global")}, nil, nil, nil, nil, 0, + futureAppID, [][]byte{[]byte("global")}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallGlobalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallGlobalTxn) a.NoError(err) // construct app optin - appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(futureAppID, nil, nil, nil, nil, nil, 0) + appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(futureAppID, nil, libgoal.RefBundle{}, 0) a.NoError(err) appOptInTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appOptInTxn) // construct app call "global" appCallLocalTxn, err := testClient.MakeUnsignedAppNoOpTx( - futureAppID, [][]byte{[]byte("local")}, nil, nil, nil, nil, 0, + futureAppID, [][]byte{[]byte("local")}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallLocalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallLocalTxn) @@ -2192,7 +2189,7 @@ end: // create app and get the application ID appCreateTxn, err := testClient.MakeUnsignedAppCreateTx( transactions.NoOpOC, approval, clearState, gl, - lc, nil, nil, nil, nil, nil, 0) + lc, nil, libgoal.RefBundle{}, 0) a.NoError(err) appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn) a.NoError(err) @@ -2212,18 +2209,18 @@ end: // construct app call "global" appCallGlobalTxn, err := testClient.MakeUnsignedAppNoOpTx( - futureAppID, [][]byte{[]byte("global")}, nil, nil, nil, nil, 0, + futureAppID, [][]byte{[]byte("global")}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallGlobalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallGlobalTxn) a.NoError(err) // construct app optin - appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(futureAppID, nil, nil, nil, nil, nil, 0) + appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(futureAppID, nil, libgoal.RefBundle{}, 0) a.NoError(err) appOptInTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appOptInTxn) // construct app call "local" appCallLocalTxn, err := testClient.MakeUnsignedAppNoOpTx( - futureAppID, [][]byte{[]byte("local")}, nil, nil, nil, nil, 0, + futureAppID, [][]byte{[]byte("local")}, libgoal.RefBundle{}, 0, ) a.NoError(err) appCallLocalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallLocalTxn) @@ -2520,7 +2517,7 @@ func TestSimulateWithUnnamedResources(t *testing.T) { lc := basics.StateSchema{} // create app - txn, err = testClient.MakeUnsignedAppCreateTx(transactions.OptInOC, alwaysApprove, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0) + txn, err = testClient.MakeUnsignedAppCreateTx(transactions.OptInOC, alwaysApprove, alwaysApprove, gl, lc, nil, libgoal.RefBundle{}, 0) a.NoError(err) txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn) a.NoError(err) @@ -2585,7 +2582,7 @@ assert // Box access byte "A" -int 1025 +int 2049 // need three refs with old quota, two after the bump (we only test latest) box_create assert @@ -2598,7 +2595,7 @@ int 1 approval := ops.Program // create app - txn, err = testClient.MakeUnsignedAppCreateTx(transactions.NoOpOC, approval, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0) + txn, err = testClient.MakeUnsignedAppCreateTx(transactions.NoOpOC, approval, alwaysApprove, gl, lc, nil, libgoal.RefBundle{}, 0) a.NoError(err) txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn) a.NoError(err) @@ -2624,7 +2621,7 @@ int 1 // construct app call txn, err = testClient.MakeUnsignedAppNoOpTx( - testAppID, nil, nil, nil, nil, nil, 0, + testAppID, nil, libgoal.RefBundle{}, 0, ) a.NoError(err) txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn) @@ -2642,7 +2639,7 @@ int 1 AllowUnnamedResources: false, }) a.NoError(err) - a.Contains(*resp.TxnGroups[0].FailureMessage, "logic eval error: invalid Account reference "+otherAddress) + a.Contains(*resp.TxnGroups[0].FailureMessage, "logic eval error: unavailable Account "+otherAddress) a.Equal([]int{0}, *resp.TxnGroups[0].FailedAt) // It should work with AllowUnnamedResources=true diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go index 08c2705733..e8c1e391d6 100644 --- a/test/e2e-go/upgrades/application_support_test.go +++ b/test/e2e-go/upgrades/application_support_test.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" @@ -150,7 +151,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) @@ -233,7 +234,7 @@ int 1 a.Equal(uint64(1), value.Uint) // call the app - tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) @@ -392,7 +393,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, round, round+basics.Round(primaryNodeUnupgradedProtocol.DefaultUpgradeWaitRounds), fee, tx) a.NoError(err) @@ -484,7 +485,7 @@ int 1 a.Equal(uint64(1), value.Uint) // call the app - tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppOptInTx(appIdx, nil, libgoal.RefBundle{}, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) diff --git a/test/scripts/e2e_subs/app-assets-access.sh b/test/scripts/e2e_subs/app-assets-access.sh new file mode 100755 index 0000000000..9258f5a55b --- /dev/null +++ b/test/scripts/e2e_subs/app-assets-access.sh @@ -0,0 +1,332 @@ +#!/bin/bash + +# This test is very similar to app-assets.sh, but uses --access pervasively. + +filename=$(basename "$0") +scriptname="${filename%.*}" +date "+${scriptname} start %Y%m%d_%H%M%S" + +my_dir="$(dirname "$0")" +source "$my_dir/rest.sh" "$@" +function rest() { + curl -q -s -H "Authorization: Bearer $PUB_TOKEN" "$NET$1" +} + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +TEAL=test/scripts/e2e_subs/tealprogs + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') +# Create a smaller account so rewards won't change balances. +SMALL=$(${gcmd} account new | awk '{ print $6 }') +# Under one algo receives no rewards +${gcmd} clerk send -a 999000 -f "$ACCOUNT" -t "$SMALL" + +function balance { + acct=$1; shift + goal account balance -a "$acct" | awk '{print $1}' +} + +[ "$(balance "$ACCOUNT")" = 999999000000 ] +[ "$(balance "$SMALL")" = 999000 ] + +function created_assets { + acct=$1; + goal account info -a "$acct" | awk '/Created Assets:/,/Held Assets:/' | grep "ID*" | awk -F'[, ]' '{print $4}' +} + +function created_supply { + acct=$1; + goal account info -a "$acct" | awk '/Created Assets:/,/Held Assets:/' | grep "ID*" | awk -F'[, ]' '{print $7}' +} + +function asset_bal { + acct=$1; + goal account info -a "$acct" | awk '/Held Assets:/,/Created Apps:/' | grep "ID*" | awk -F'[, ]' '{print $7}' +} + +function asset_ids { + acct=$1; + goal account info -a "$acct" | awk '/Held Assets:/,/Created Apps:/' | grep "ID*" | awk -F'[, ]' '{print $2}' +} +# +function assets { + acct=$1; + goal account info -a "$acct" | awk '/Held Assets:/,/Created Apps:/' | grep "ID*" | awk -F'[, ]' '{print $4}' +} + +APPID=$(${gcmd} app create --creator "${SMALL}" --approval-prog=${TEAL}/assets-escrow9.teal --global-byteslices 4 --global-ints 0 --local-byteslices 0 --local-ints 1 --clear-prog=<(printf '#pragma version 9\nint 1') | grep Created | awk '{ print $6 }') +[ "$(balance "$SMALL")" = 998000 ] # 1000 fee + +# Use --access on all app calls +function appl { + method=$1; shift + ${gcmd} app call --app-id="$APPID" --app-arg="str:$method" --access "$@" +} + +function app-txid { + # When app (call or optin) submits, this is how the txid is + # printed. Not in appl() because appl is also used with -o to + # create tx + grep -o -E 'txid [A-Z0-9]{52}' | cut -c 6- | head -1 +} + +function asset-id { + grep -o -E 'index [A-Z0-9]+'| cut -c 7- +} + +APPACCT=$(python -c "import algosdk.encoding as e; print(e.encode_address(e.checksum(b'appID'+($APPID).to_bytes(8, 'big'))))") +EXAMPLE_URL="http://example.com" +function asset-create { + amount=$1; shift + ${gcmd} asset create --creator "$SMALL" --total "$amount" --decimals 0 "$@" --asseturl "$EXAMPLE_URL" +} + +function asset-deposit { + amount=$1;shift + ID=$1; shift + ${gcmd} asset send -f "$SMALL" -t "$APPACCT" -a "$amount" --assetid "$ID" "$@" +} + +function asset-optin { + ${gcmd} asset optin "$@" +} + +function clawback_addr { + grep -o -E 'Clawback address: [A-Z0-9]{58}' | awk '{print $3}' +} + +function asset_url { + grep -o -E 'URL:.*'|awk '{print $2}' +} + +function payin { + amount=$1; shift + ${gcmd} clerk send -f "$SMALL" -t "$APPACCT" -a "$amount" "$@" +} + +T=$TEMPDIR + +function sign { + ${gcmd} clerk sign -i "$T/$1.tx" -o "$T/$1.stx" +} + +TXID=$(${gcmd} app optin --app-id "$APPID" --from "${SMALL}" | app-txid) +# Rest succeeds, no stray inner-txn array +[ "$(rest "/v2/transactions/pending/$TXID" | jq '.["inner-txn"]')" == null ] +[ "$(balance "$SMALL")" = 997000 ] # 1000 fee + +ASSETID=$(asset-create 1000000 --name "e2e" --unitname "e" | asset-id) +[ "$(balance "$SMALL")" = 996000 ] # 1000 fee + +${gcmd} clerk send -a 999000 -f "$ACCOUNT" -t "$APPACCT" +! appl "optin(uint64):void" --app-arg "int:$ASSETID" --foreign-asset="$ASSETID" --from="$SMALL" || exit 1 +appl "optin(uint64):void" --app-arg "int:$ASSETID" --foreign-asset="$ASSETID" --from="$SMALL" --holding "$ASSETID+app($APPID)" +[ "$(balance "$APPACCT")" = 998000 ] # 1000 fee +[ "$(balance "$SMALL")" = 995000 ] + +# Deposit is exactly like app-assets.sh only sender's local state is accessed +appl "deposit():void" -o "$T/deposit.tx" --from="$SMALL" +asset-deposit 1000 $ASSETID -o "$T/axfer1.tx" +cat "$T/deposit.tx" "$T/axfer1.tx" | ${gcmd} clerk group -i - -o "$T/group.tx" +sign group +${gcmd} clerk rawsend -f "$T/group.stx" + +[ "$(asset_ids "$SMALL")" = $ASSETID ] # asset ID +[ "$(asset_bal "$SMALL")" = 999000 ] # asset balance +[ "$(asset_ids "$APPACCT")" = $ASSETID ] +[ "$(asset_bal "$APPACCT")" = 1000 ] +[ "$(balance "$SMALL")" = 993000 ] # 2 fees +[ "$(balance "$APPACCT")" = 998000 ] + +# Withdraw 100 in app. Confirm that inner txn is visible to transaction API. +! appl "withdraw(uint64,uint64):void" --app-arg="int:$ASSETID" --app-arg="int:100" --foreign-asset="$ASSETID" --from="$SMALL" || exit 1 +WITHDRAW=("withdraw(uint64,uint64):void" --app-arg="int:$ASSETID" --from="$SMALL" --holding "$ASSETID+app($APPID),$ASSETID+$SMALL") +TXID=$(appl "${WITHDRAW[@]}" --app-arg="int:100" | app-txid) +[ "$(rest "/v2/transactions/pending/$TXID" \ + | jq '.["inner-txns"][0].txn.txn.aamt')" = 100 ] +[ "$(rest "/v2/transactions/pending/$TXID?format=msgpack" | msgpacktool -d \ + | jq '.["inner-txns"][0].txn.txn.type')" = '"axfer"' ] +# Now confirm it's in blocks API (this time in our internal form) +ROUND=$(rest "/v2/transactions/pending/$TXID" | jq '.["confirmed-round"]') +rest "/v2/blocks/$ROUND" | jq .block.txns[0].dt.itx + +[ "$(asset_bal "$SMALL")" = 999100 ] # 100 asset withdrawn +[ "$(asset_bal "$APPACCT")" = 900 ] # 100 asset withdrawn +[ "$(balance "$SMALL")" = 992000 ] # 1 fee +[ "$(balance "$APPACCT")" = 997000 ] # fee paid by app + +appl "${WITHDRAW[@]}" --app-arg="int:100" --fee 2000 +[ "$(asset_bal "$SMALL")" = 999200 ] # 100 asset withdrawn +[ "$(asset_bal "$APPACCT")" = 800 ] # 100 asset withdrawn +[ "$(balance "$SMALL")" = 990000 ] # 2000 fee +[ "$(balance "$APPACCT")" = 997000 ] # fee credit used + +# Try to withdraw too much +! appl "${WITHDRAW[@]}" --app-arg="int:1000" || exit 1 +[ "$(asset_bal "$SMALL")" = 999200 ] # no changes +[ "$(asset_bal "$APPACCT")" = 800 ] +[ "$(balance "$SMALL")" = 990000 ] +[ "$(balance "$APPACCT")" = 997000 ] + +# Show that it works AT exact asset balance +appl "${WITHDRAW[@]}" --app-arg="int:800" +[ "$(asset_bal "$SMALL")" = 1000000 ] +[ "$(asset_bal "$APPACCT")" = 0 ] +[ "$(balance "$SMALL")" = 989000 ] +[ "$(balance "$APPACCT")" = 996000 ] # app paid the fee for inner axfer + +USER=$(${gcmd} account new | awk '{ print $6 }') #new account +${gcmd} clerk send -a 999000 -f "$ACCOUNT" -t "$USER" #fund account +asset-optin --assetid "$ASSETID" -a $USER #opt in to asset +# SET $USER as clawback address +${gcmd} asset config --manager $SMALL --assetid $ASSETID --new-clawback $USER +cb_addr=$(${gcmd} asset info --assetid $ASSETID | clawback_addr) +[ "$cb_addr" = "$USER" ] +url=$(${gcmd} asset info --assetid $ASSETID | asset_url) +[ "$url" = "$EXAMPLE_URL" ] +${gcmd} asset send -f "$SMALL" -t "$USER" -a "1000" --assetid "$ASSETID" --clawback "$USER" +[ $(asset_bal "$USER") = 1000 ] +[ $(asset_bal "$SMALL") = 999000 ] +# rekey $USER to "$APPACCT" +${gcmd} clerk send --from "$USER" --to "$USER" -a 0 --rekey-to "$APPACCT" +# $USER should still have clawback auth. should have been authorized by "$APPACCT" +${gcmd} asset send -f "$SMALL" -t "$USER" -a "1000" --assetid "$ASSETID" --clawback "$USER" && exit 1 + +USER2=$(${gcmd} account new | awk '{ print $6 }') #new account +${gcmd} clerk send -a 999000 -f "$ACCOUNT" -t "$USER2" #fund account +asset-optin --assetid "$ASSETID" -a $USER2 #opt in to asset +# set $APPACCT as clawback address on asset +${gcmd} asset config --manager $SMALL --assetid $ASSETID --new-clawback $APPACCT +cb_addr=$(${gcmd} asset info --assetid $ASSETID | clawback_addr) +[ "$cb_addr" = "$APPACCT" ] #app is set as clawback address +# transfer asset from $SMALL to $USER +# With just the accounts and asset, won't work b/c the holdings are required +RES=$(appl "transfer(uint64,uint64,address):void" --from="$SMALL" \ + --app-arg="int:$ASSETID" --app-arg="int:1000" --app-arg="addr:$USER2" \ + --foreign-asset="$ASSETID" --app-account="$USER2" 2>&1) && { + date '+app-assets-access FAIL transfer should fail without explicit holding %Y%m%d_%H%M%S' + exit 1 +} +# allowsAssetTransfer checks the AssetReceiver before AssetSender, so we get that error. +[[ $RES == *"unavailable Holding $ASSETID+$USER2"* ]] || exit 1 + +# Need access to both holdings. +appl "transfer(uint64,uint64,address):void" \ + --from="$SMALL" --holding="$ASSETID+$SMALL" \ + --app-arg="int:$ASSETID" --app-arg="int:1000" \ + --app-arg="addr:$USER2" --holding="$ASSETID+$USER2" + + +[ $(asset_bal "$USER2") = 1000 ] +[ $(asset_bal "$SMALL") = 998000 ] +# transfer asset from $USER2 to $SMALL (this invocation tries to just +# specify the asset and the recipient account) It fails because with +# --access, cross-products are not implicitly available. +RES=$(appl "transfer(uint64,uint64,address):void" --from="$USER2" \ + --app-arg="int:$ASSETID" --app-arg="int:100" --foreign-asset="$ASSETID" \ + --app-arg="addr:$SMALL" --app-account="$SMALL" 2>&1) && { + date '+app-assets FAIL transfer using --access should fail without explicit holding %Y%m%d_%H%M%S' + exit 1 +} +[[ $RES == *"unavailable Holding"* ]] || exit 1 + +appl "transfer(uint64,uint64,address):void" --from="$USER2" --holding="$ASSETID+$USER2" \ + --app-arg="int:$ASSETID" --app-arg="int:100" \ + --app-arg="addr:$SMALL" --holding="$ASSETID+$SMALL" + +[ $(asset_bal "$USER2") = 900 ] +[ $(asset_bal "$SMALL") = 998100 ] + +# opt in more assets. --holding for the app/asset is needed +ASSETID2=$(asset-create 1000000 --name "alpha" --unitname "a" | asset-id) +appl "optin(uint64):void" --app-arg "int:$ASSETID2" --foreign-asset="$ASSETID2" --from="$SMALL" --holding "$ASSETID2+app($APPID)" +ASSETID3=$(asset-create 1000000 --name "beta" --unitname "b" | asset-id) +appl "optin(uint64):void" --app-arg "int:$ASSETID3" --foreign-asset="$ASSETID3" --from="$SMALL" --holding "$ASSETID3+app($APPID)" + +IDs="$ASSETID +$ASSETID2 +$ASSETID3" +[[ "$(asset_ids "$APPACCT")" = $IDs ]] || exit 1 # account has 3 assets + +# opt out of assets +RES=$(appl "close(uint64):void" --from="$SMALL" --app-arg "int:$ASSETID2" --foreign-asset="$ASSETID2" 2>&1) && { + date '+app-assets FAIL close using --access should fail without explicit app holding %Y%m%d_%H%M%S' + exit 1 +} +[[ $RES == *"unavailable Holding $ASSETID2+$APPACCT"* ]] || exit 1 # app can't close itself unless its holding is available + +# add that holding, but still not enough... +RES=$(appl "close(uint64):void" --from="$SMALL" --app-arg "int:$ASSETID2" --holding="$ASSETID2+$APPACCT" 2>&1) && { + date '+app-assets FAIL close using --access should fail without explicit sender holding %Y%m%d_%H%M%S' + exit 1 +} +[[ $RES == *"unavailable Holding $ASSETID2+$SMALL"* ]] || exit 1 # app closes to sender, so needs that holding too + +appl "close(uint64):void" --from="$SMALL" --app-arg "int:$ASSETID2" \ + --holding="$ASSETID2+$APPACCT,$ASSETID2+$SMALL" +IDs="$ASSETID +$ASSETID3" +[[ "$(asset_ids "$APPACCT")" = $IDs ]] || exit 1 # account has 2 assets +appl "close(uint64):void" --from="$SMALL" --app-arg "int:$ASSETID" \ + --holding="$ASSETID+$APPACCT,$ASSETID+$SMALL" +appl "close(uint64):void" --from="$SMALL" --app-arg "int:$ASSETID3" \ + --holding="$ASSETID3+$APPACCT,$ASSETID3+$SMALL" +[[ "$(asset_ids "$APPACCT")" = "" ]] || exit 1 # account has no assets + +# app creates asset +appl "create(uint64):void" --app-arg="int:1000000" --from="$SMALL" +[ "$(created_assets "$APPACCT")" = "X" ] +[ "$(created_supply "$APPACCT")" = 1000000 ] + +# mint asset +APPASSETID=$(asset_ids "$APPACCT") +asset-optin --assetid "$APPASSETID" -a $SMALL #opt in to asset +appl "mint(uint64):void" --from="$SMALL" --app-arg "int:$APPASSETID" \ + --holding="$APPASSETID+app($APPID)" --holding="$APPASSETID+$SMALL" \ + -o "$T/mint.tx" +payin 1000 -o "$T/pay1.tx" +cat "$T/mint.tx" "$T/pay1.tx" | ${gcmd} clerk group -i - -o "$T/group.tx" +sign group +${gcmd} clerk rawsend -f "$T/group.stx" + +IDs="$ASSETID +$ASSETID2 +$ASSETID3 +$APPASSETID" +[[ "$(asset_ids "$SMALL")" = $IDs ]] || exit 1 # has new asset +[ "$(asset_bal "$SMALL" | awk 'FNR==4{print $0}')" = 1000 ] # correct balances +[ "$(asset_bal "$APPACCT")" = 999000 ] # 1k sent + +# freeze asset +RES=$(appl "freeze(uint64,bool):void" --from="$SMALL" --app-arg="int:$APPASSETID" --app-arg="int:1" --foreign-asset="$APPASSETID" 2>&1) && { + date '+app-assets FAIL freeze using --access should fail without explicit sender holding %Y%m%d_%H%M%S' + exit 1 +} +[[ $RES == *"unavailable Holding $APPASSETID+$SMALL"* ]] || exit 1 +appl "freeze(uint64,bool):void" --from="$SMALL" --app-arg="int:$APPASSETID" --app-arg="int:1" --holding="$APPASSETID+$SMALL" + +# fail since asset is frozen on $SMALL +appl "mint(uint64):void" --from="$SMALL" --app-arg="int:$APPASSETID" \ + --holding="$APPASSETID+app($APPID)" --holding="$APPASSETID+$SMALL" \ + -o "$T/mint.tx" +payin 1000 -o "$T/pay1.tx" +cat "$T/mint.tx" "$T/pay1.tx" | ${gcmd} clerk group -i - -o "$T/group.tx" +sign group +${gcmd} clerk rawsend -f "$T/group.stx" && exit 1 # fail or exit + + +# unfreeze asset +appl "freeze(uint64,bool):void" --app-arg="int:$APPASSETID" --app-arg="int:0" --holding="$APPASSETID+$SMALL" --from="$SMALL" +# try to resend that same group +${gcmd} clerk rawsend -f "$T/group.stx" # try again +[ "$(asset_bal "$SMALL" | awk 'FNR==4{print $0}')" = 2000 ] # minted 1000 + +date "+${scriptname} OK %Y%m%d_%H%M%S" diff --git a/test/scripts/e2e_subs/app-assets.sh b/test/scripts/e2e_subs/app-assets.sh index 98171a6160..5543efe783 100755 --- a/test/scripts/e2e_subs/app-assets.sh +++ b/test/scripts/e2e_subs/app-assets.sh @@ -202,15 +202,15 @@ ${gcmd} asset config --manager $SMALL --assetid $ASSETID --new-clawback $APPACCT cb_addr=$(${gcmd} asset info --assetid $ASSETID | clawback_addr) [ "$cb_addr" = "$APPACCT" ] #app is set as clawback address # transfer asset from $SMALL to $USER -appl "transfer(uint64):void" --app-arg="int:1000" --foreign-asset="$ASSETID" --from="$SMALL" --app-account="$USER2" +appl "transfer(uint64):void" --app-arg="int:1000" --foreign-asset="$ASSETID" --from="$SMALL" --app-account="$USER2" [ $(asset_bal "$USER2") = 1000 ] [ $(asset_bal "$SMALL") = 998000 ] # transfer asset from $USER to $SMALL -appl "transfer(uint64):void" --app-arg="int:100" --foreign-asset="$ASSETID" --from="$USER2" --app-account="$SMALL" +appl "transfer(uint64):void" --app-arg="int:100" --foreign-asset="$ASSETID" --from="$USER2" --app-account="$SMALL" + [ $(asset_bal "$USER2") = 900 ] [ $(asset_bal "$SMALL") = 998100 ] -# opt in more assets ASSETID2=$(asset-create 1000000 --name "alpha" --unitname "a" | asset-id) appl "optin():void" --foreign-asset="$ASSETID2" --from="$SMALL" ASSETID3=$(asset-create 1000000 --name "beta" --unitname "b" | asset-id) @@ -219,16 +219,16 @@ appl "optin():void" --foreign-asset="$ASSETID3" --from="$SMALL" IDs="$ASSETID $ASSETID2 $ASSETID3" -[[ "$(asset_ids "$APPACCT")" = $IDs ]] # account has 3 assets +[[ "$(asset_ids "$APPACCT")" = $IDs ]] || exit 1 # account has 3 assets # opt out of assets appl "close():void" --foreign-asset="$ASSETID2" --from="$SMALL" IDs="$ASSETID $ASSETID3" -[[ "$(asset_ids "$APPACCT")" = $IDs ]] # account has 2 assets +[[ "$(asset_ids "$APPACCT")" = $IDs ]] || exit 1 # account has 2 assets appl "close():void" --foreign-asset="$ASSETID" --from="$SMALL" appl "close():void" --foreign-asset="$ASSETID3" --from="$SMALL" -[[ "$(asset_ids "$APPACCT")" = "" ]] # account has no assets +[[ "$(asset_ids "$APPACCT")" = "" ]] || exit 1 # account has no assets # app creates asset appl "create(uint64):void" --app-arg="int:1000000" --from="$SMALL" @@ -248,7 +248,7 @@ IDs="$ASSETID $ASSETID2 $ASSETID3 $APPASSETID" -[[ "$(asset_ids "$SMALL")" = $IDs ]] # has new asset +[[ "$(asset_ids "$SMALL")" = $IDs ]] || exit 1 # has new asset [ "$(asset_bal "$SMALL" | awk 'FNR==4{print $0}')" = 1000 ] # correct balances [ "$(asset_bal "$APPACCT")" = 999000 ] # 1k sent diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 71f9a22b04..c09e1f0fe7 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -466,7 +466,7 @@ if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONS false fi -EXPECTED_FAILURE="logic eval error: invalid Account reference $OTHERADDR" +EXPECTED_FAILURE="logic eval error: unavailable Account $OTHERADDR" if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"${EXPECTED_FAILURE}"* ]]; then date '+app-simulate-test FAIL the app call without allow unnamed resources should fail with the expected error %Y%m%d_%H%M%S' diff --git a/test/scripts/e2e_subs/e2e-app-x-app-reads.sh b/test/scripts/e2e_subs/e2e-app-x-app-reads.sh index 86b61339ea..5ffd6f7e79 100755 --- a/test/scripts/e2e_subs/e2e-app-x-app-reads.sh +++ b/test/scripts/e2e_subs/e2e-app-x-app-reads.sh @@ -17,12 +17,19 @@ gcmd="goal -w ${WALLET}" ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') # Create an app with global state "foo" = "xxx" -APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/globwrite.teal --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 --app-arg "str:xxx" --clear-prog <(printf '#pragma version 2\nint 1') | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog "$DIR/tealprogs/globwrite.teal" --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 --app-arg "str:xxx" --clear-prog <(printf '#pragma version 2\nint 1') | grep Created | awk '{ print $6 }') # Creating an app that attempts to read APPID's global state without setting -# foreignapps should fail -EXPERR="App index 1 beyond txn.ForeignApps" -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/xappreads.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 2\nint 1') 2>&1 || true) +# --foreign-app should fail +EXPERR="unavailable App 1" +RES=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog "$DIR/tealprogs/xappreads.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 9\nint 1') 2>&1 || true) +if [[ $RES != *"$EXPERR"* ]]; then + date '+x-app-reads FAIL expected unavailable app slot %Y%m%d_%H%M%S' + false +fi + +# Same result when using --access (even though --foreign-asset makes the slot "legal", just not an app) +RES=$(${gcmd} app create --creator "$ACCOUNT" --access --foreign-asset "$APPID" --approval-prog "$DIR/tealprogs/xappreads.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 9\nint 1') 2>&1 || true) if [[ $RES != *"$EXPERR"* ]]; then date '+x-app-reads FAIL expected disallowed foreign global read to fail %Y%m%d_%H%M%S' false @@ -31,16 +38,25 @@ fi # Creating an app that attempts to read APPID's global state and compare with # "bar" should make it past the foreign-app check, but fail since # "xxx" != "bar" -EXPERR="rejected by ApprovalProgram" -RES=$(${gcmd} app create --creator ${ACCOUNT} --foreign-app $APPID --approval-prog ${DIR}/tealprogs/xappreads.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 2\nint 1') 2>&1 || true) +EXPERR='"bar"; ==; assert' +RES=$(${gcmd} app create --creator "$ACCOUNT" --foreign-app "$APPID" --approval-prog "$DIR/tealprogs/xappreads.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 9\nint 1') 2>&1 || true) if [[ $RES != *"$EXPERR"* ]]; then - date '+x-app-reads FAIL expected disallowed foreign global read to fail %Y%m%d_%H%M%S' + date '+x-app-reads FAIL expected foreign global mismatched read to fail %Y%m%d_%H%M%S' + false +fi + +# Same result with --access +RES=$(${gcmd} app create --creator "$ACCOUNT" --access --foreign-app "$APPID" --approval-prog "$DIR/tealprogs/xappreads.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 9\nint 1') 2>&1 || true) +if [[ $RES != *"$EXPERR"* ]]; then + date '+x-app-reads FAIL expected foreign global mismatched read to fail with --access %Y%m%d_%H%M%S' false fi # Update value at "foo" to be "bar" in app $APPID -${gcmd} app call --app-id $APPID --from $ACCOUNT --app-arg "str:bar" +${gcmd} app call --app-id "$APPID" --from "$ACCOUNT" --app-arg "str:bar" # Creating other app should now succeed with properly set foreignapps -AARGS="{args: [{encoding: \"int\", value: \"$APPID\"}], foreignapps: [$APPID]}" -${gcmd} app create --creator ${ACCOUNT} --foreign-app $APPID --approval-prog ${DIR}/tealprogs/xappreads.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 2\nint 1') +${gcmd} app create --creator "$ACCOUNT" --foreign-app "$APPID" --approval-prog "$DIR/tealprogs/xappreads.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 9\nint 1') + +# Using --access also works +${gcmd} app create --creator "$ACCOUNT" --access --foreign-app "$APPID" --approval-prog "$DIR/tealprogs/xappreads.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 9\nint 1') diff --git a/test/scripts/e2e_subs/shared-resources.py b/test/scripts/e2e_subs/shared-resources.py index 2df56f7893..7899a583f2 100755 --- a/test/scripts/e2e_subs/shared-resources.py +++ b/test/scripts/e2e_subs/shared-resources.py @@ -52,7 +52,7 @@ # Won't work, because v8 can't modify an account (goal.account) that # isn't in the `grp2` txn assert err -assert "invalid Account reference "+goal.account in str(err) +assert "unavailable Account "+goal.account in str(err) # Now, upgrade program to same thing, but v9 diff --git a/test/scripts/e2e_subs/tealprogs/assets-escrow9.teal b/test/scripts/e2e_subs/tealprogs/assets-escrow9.teal new file mode 100644 index 0000000000..8c26f060fb --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/assets-escrow9.teal @@ -0,0 +1,340 @@ +#pragma version 9 + // This is intended to be very similar to assets-escrow.teal, + // but show how such an application would be written with + // tx.Access (or shared resources) in mind. All resources are + // specified as app args, so that inspecting the txn local + // foreign-arrays is avoided. + + // This application accepts these actions on assets + // optin(uint64):void - opt in the app to an asset. + // close(uint64):void - opt out the app from an asset + // deposit():void - deposit assets on app and hold until withdraw is requested; + // update the asset balance in app's local state + // withdraw(uint64,uint64):void - withdraw arg 1 units of arg 2 asset and update the asset balance in app's local state. + // approve if withdraw amount < balance + // transfer(uint64,uint64,address,address):void - app has clawback auth to do it (asset, amount, from, to) + // create(uint64):void - app creates assets + // mint(uint64):void - withdraw assets created by app + // freeze(uint64,bool):void - freeze/unfreeze an asset on an account + + // ApplicationID is zero in inital creation txn + txn ApplicationID + bz handle_createapp + + // Handle possible OnCompletion type. We don't have to + // worry about handling ClearState, because the + // ClearStateProgram will execute in that case, not the + // ApprovalProgram. + + txn OnCompletion + int NoOp + == + bnz handle_noop + + txn OnCompletion + int OptIn + == + bnz handle_optin + + txn OnCompletion + int CloseOut + == + bnz handle_closeout + + txn OnCompletion + int UpdateApplication + == + bnz handle_updateapp + + txn OnCompletion + int DeleteApplication + == + bnz handle_deleteapp + // Unexpected OnCompletion value. Should be unreachable. + err + +handle_createapp: + int 1 + return + +handle_optin: + // Let anyone optin with a single txn, with no arguments. If + // it's not a single txn, fall through to handle_noop, so that + // a deposit can be made while opting in. + // We should standardize a behaviour like this in ABI. + global GroupSize + int 1 + == + bz handle_noop + int 1 + return + +handle_noop: + // opt in app to asset to enable axfer + txn ApplicationArgs 0 + byte "optin(uint64):void" + == + bz not_optin + byte "optin" + callsub debug + + itxn_begin + int axfer + itxn_field TypeEnum + + int 0 + itxn_field AssetAmount + + txna ApplicationArgs 1 + btoi + itxn_field XferAsset + + global CurrentApplicationAddress + itxn_field AssetReceiver + itxn_submit + + int 1 + return +not_optin: + txn ApplicationArgs 0 + byte "deposit():void" + == + bz not_deposit + + byte "deposit" + callsub debug + + // Handle a deposit. Next txn slot must axfer our app account + txn GroupIndex + int 1 + + + dup + dup + + gtxns TypeEnum + int axfer + == + assert + + gtxns AssetReceiver + global CurrentApplicationAddress + == + assert + + gtxns AssetAmount + + // Track the amount this sender deposited in their local state + int 0 + byte "balance" + dup2 + app_local_get + uncover 3 // pull up the Amount + + + app_local_put + + int 1 + return +not_deposit: + txn ApplicationArgs 0 + byte "withdraw(uint64,uint64):void" + == + bz not_withdraw + + // Handle withdraw. + + int 0 + byte "balance" + dup2 + app_local_get + + // Subtract the request and replace. Rejects on underflow + txn ApplicationArgs 2 + btoi + - + app_local_put + + itxn_begin + int axfer + itxn_field TypeEnum + + txn ApplicationArgs 1 + btoi + itxn_field XferAsset + + txn ApplicationArgs 2 + btoi + itxn_field AssetAmount + + txn Sender + itxn_field AssetReceiver + itxn_submit + + int 1 + return +not_withdraw: + txn ApplicationArgs 0 + byte "close(uint64):void" + == + bz not_close + + // Handle close. + itxn_begin + int axfer + itxn_field TypeEnum + + txna ApplicationArgs 1 + btoi + itxn_field XferAsset + + int 0 + itxn_field AssetAmount + + txn Sender + itxn_field AssetReceiver + + txn Sender + itxn_field AssetCloseTo + itxn_submit + + int 1 + return +not_close: + txn ApplicationArgs 0 + byte "transfer(uint64,uint64,address):void" + == + bz not_transfer + + // Handle transfer. + itxn_begin + int axfer + itxn_field TypeEnum + + txn ApplicationArgs 1 + btoi + itxn_field XferAsset + + txn ApplicationArgs 2 + btoi + itxn_field AssetAmount + + txn Sender + itxn_field AssetSender + + txna ApplicationArgs 3 + itxn_field AssetReceiver + + itxn_submit + + int 1 + return + +not_transfer: + txn ApplicationArgs 0 + byte "create(uint64):void" + == + bz not_create + // Handle create. + itxn_begin + int acfg + itxn_field TypeEnum + + txn ApplicationArgs 1 + btoi + itxn_field ConfigAssetTotal + int 0 + itxn_field ConfigAssetDecimals + byte "x" + itxn_field ConfigAssetUnitName + byte "X" + itxn_field ConfigAssetName + global CurrentApplicationAddress + itxn_field ConfigAssetFreeze + + itxn_submit + int 1 + return +not_create: + txn ApplicationArgs 0 + byte "mint(uint64):void" + == + bz not_mint + // Handle mint. Next txn slot must pay our app account + txn GroupIndex + int 1 + + + dup + dup + + gtxns TypeEnum + int pay + == + assert + + gtxns Receiver + global CurrentApplicationAddress + == + assert + + // mint asset + itxn_begin + int axfer + itxn_field TypeEnum + + txn ApplicationArgs 1 + btoi + itxn_field XferAsset + + gtxns Amount + itxn_field AssetAmount + + txn Sender + itxn_field AssetReceiver + itxn_submit + + int 1 + return +not_mint: + txn ApplicationArgs 0 + byte "freeze(uint64,bool):void" + == + bz not_freeze + + //Handle freeze + itxn_begin + int afrz + itxn_field TypeEnum + + txn ApplicationArgs 1 + btoi + itxn_field FreezeAsset + + txn ApplicationArgs 2 + btoi + itxn_field FreezeAssetFrozen + + txn Sender + itxn_field FreezeAssetAccount + + itxn_submit + + int 1 + return +not_freeze: + // Unknown call "method" + err + +handle_closeout: + int 1 + return + +handle_updateapp: +handle_deleteapp: + txn Sender + global CreatorAddress + == + return +debug: + byte "debug" + swap + app_global_put + retsub diff --git a/test/scripts/e2e_subs/tealprogs/xappreads.teal b/test/scripts/e2e_subs/tealprogs/xappreads.teal index 90741bb381..29ee7210e7 100644 --- a/test/scripts/e2e_subs/tealprogs/xappreads.teal +++ b/test/scripts/e2e_subs/tealprogs/xappreads.teal @@ -1,4 +1,6 @@ -#pragma version 2 +#pragma version 9 +// We use version 9 so we can test --access which needs at least sharedResourcesVersion + // Fetch app idx we want to read global state from int 1 // ForeignApps index // non-utf8 key @@ -8,20 +10,11 @@ byte 0xfefeffef0000112233 app_global_get_ex // Should exist -bz fail +assert // Value should be "bar" -byte base64 YmFy +byte "bar" == -bz fail - -// Test passed -b succeed - -fail: -int 0 -return +assert -succeed: int 1 -return diff --git a/util/fn.go b/util/fn.go new file mode 100644 index 0000000000..34372a5cd0 --- /dev/null +++ b/util/fn.go @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2025 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package util + +/* Functions inspired by functional languages. */ + +// Map applies a function to each element of the input slice and returns a new +// slice with the transformed elements. A nil slice returns nil. +func Map[X any, Y any](input []X, fn func(X) Y) []Y { + // preserve nil-ness + if input == nil { + return nil + } + + output := make([]Y, len(input)) + for i := range input { + output[i] = fn(input[i]) + } + return output +} + +// MapErr applies a function to each element of the input slice and returns a +// new slice with the transformed elements. If the function returns a non-nil +// error, MapErr returns immediately with a nil slice and the error. +func MapErr[X any, Y any](input []X, fn func(X) (Y, error)) ([]Y, error) { + // preserve nil-ness + if input == nil { + return nil, nil + } + + output := make([]Y, len(input)) + for i := range input { + y, err := fn(input[i]) + if err != nil { + return nil, err + } + output[i] = y + } + return output, nil +} From a59652c68a33fa415093023965123279bc64bafd Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 7 Aug 2025 09:21:27 -0400 Subject: [PATCH 12/25] tests: fix wait timeout in TestApplicationsUpgradeOverREST (#6410) --- test/e2e-go/upgrades/application_support_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go index e8c1e391d6..4e8f04b8cf 100644 --- a/test/e2e-go/upgrades/application_support_test.go +++ b/test/e2e-go/upgrades/application_support_test.go @@ -242,10 +242,10 @@ int 1 a.NoError(err) round, err = client.CurrentRound() a.NoError(err) - _, err = client.BroadcastTransaction(signedTxn) + txid, err := client.BroadcastTransaction(signedTxn) a.NoError(err) - client.WaitForRound(round + 2) + client.WaitForConfirmedTxn(round+10, txid) // check creator's balance record for the app entry and the state changes ad, err = client.AccountData(creator) From 22b07d069712b247f3de31c01bc6b3285e26981c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 15 Aug 2025 10:06:09 -0400 Subject: [PATCH 13/25] AVM: Allow access to boxes for apps made in the same group without explicit boxrefs (#6309) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Gary Malouf <982483+gmalouf@users.noreply.github.com> --- Makefile | 11 +- config/consensus.go | 7 + daemon/algod/api/server/v2/handlers.go | 2 +- daemon/algod/api/server/v2/utils.go | 25 ++- data/basics/overflow.go | 17 ++ data/basics/units_test.go | 36 ++++ data/transactions/logic/box.go | 66 ++++-- data/transactions/logic/eval.go | 36 +++- data/transactions/logic/evalStateful_test.go | 18 +- data/transactions/logic/resources.go | 18 +- ledger/boxtxn_test.go | 175 +++++++++++++++- ledger/simulation/resources.go | 202 ++++++++++++++----- ledger/simulation/simulation_eval_test.go | 106 ++++++++-- ledger/simulation/testing/utils.go | 2 +- 14 files changed, 602 insertions(+), 119 deletions(-) diff --git a/Makefile b/Makefile index 76f4e27ce4..759e13d5ca 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,6 @@ export SHORT_PART_PERIOD_FLAG := -s endif GOTAGS := --tags "$(GOTAGSLIST)" -GOTRIMPATH := $(shell GOPATH=$(GOPATH) && go help build | grep -q .-trimpath && echo -trimpath) GOLDFLAGS_BASE := -X github.com/algorand/go-algorand/config.BuildNumber=$(BUILDNUMBER) \ -X github.com/algorand/go-algorand/config.CommitHash=$(COMMITHASH) \ @@ -277,11 +276,11 @@ build: buildsrc buildsrc-special buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_exporter NONGO_BIN - $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... + $(GO_INSTALL) -trimpath $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... buildsrc-special: cd tools/block-generator && \ - $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... + $(GO_INSTALL) -trimpath $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... check-go-version: ./scripts/check_golang_version.sh build @@ -292,15 +291,15 @@ check-go-version: ## the incredible performance impact of -race on Scrypt. build-race: build @mkdir -p $(GOBIN)-race - GOBIN=$(GOBIN)-race go install $(GOTRIMPATH) $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" ./... + GOBIN=$(GOBIN)-race go install -trimpath $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" ./... cp $(GOBIN)/kmd $(GOBIN)-race # Build binaries needed for e2e/integration tests build-e2e: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a @mkdir -p $(GOBIN)-race # Build regular binaries (kmd, algod, goal) and race binaries in parallel - $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./cmd/kmd ./cmd/algod ./cmd/goal & \ - GOBIN=$(GOBIN)-race go install $(GOTRIMPATH) $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" ./cmd/goal ./cmd/algod ./cmd/algoh ./cmd/tealdbg ./cmd/msgpacktool ./cmd/algokey ./tools/teal/algotmpl ./test/e2e-go/cli/tealdbg/cdtmock & \ + $(GO_INSTALL) -trimpath $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./cmd/kmd ./cmd/algod ./cmd/goal & \ + GOBIN=$(GOBIN)-race go install -trimpath $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" ./cmd/goal ./cmd/algod ./cmd/algoh ./cmd/tealdbg ./cmd/msgpacktool ./cmd/algokey ./tools/teal/algotmpl ./test/e2e-go/cli/tealdbg/cdtmock & \ wait cp $(GOBIN)/kmd $(GOBIN)-race diff --git a/config/consensus.go b/config/consensus.go index 74bceadcfe..08fb39df6f 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -535,6 +535,11 @@ type ConsensusParams struct { // EnableBoxRefNameError specifies that box ref names should be validated early EnableBoxRefNameError bool + // EnableUnnamedBoxAccessInNewApps allows newly created (in this group) apps to + // create boxes that were not named in a box ref. Each empty box ref in the + // group allows one such creation. + EnableUnnamedBoxAccessInNewApps bool + // ExcludeExpiredCirculation excludes expired stake from the total online stake // used by agreement for Circulation, and updates the calculation of StateProofOnlineTotalWeight used // by state proofs to use the same method (rather than excluding stake from the top N stakeholders as before). @@ -1435,6 +1440,8 @@ func initConsensusProtocols() { vFuture.EnableAppVersioning = true // if not promoted when v12 goes into effect, update logic/field.go vFuture.EnableSha512BlockHash = true + vFuture.EnableUnnamedBoxAccessInNewApps = true + // txn.Access work vFuture.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same vFuture.MaxAppAccess = 16 // Twice as many, though cross products are explicit diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 78449fa7c2..b0f910f29a 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1318,7 +1318,7 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT } } - response := convertSimulationResult(simulationResult) + response := convertSimulationResult(simulationResult, proto.EnableUnnamedBoxAccessInNewApps) handle, contentType, err := getCodecHandle((*string)(params.Format)) if err != nil { diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 606a88bd73..36d72144c0 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -462,13 +462,13 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra } } -func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult { +func convertTxnResult(txnResult simulation.TxnResult, simplify bool) PreEncodedSimulateTxnResult { result := PreEncodedSimulateTxnResult{ Txn: ConvertInnerTxn(&txnResult.Txn), AppBudgetConsumed: omitEmpty(txnResult.AppBudgetConsumed), LogicSigBudgetConsumed: omitEmpty(txnResult.LogicSigBudgetConsumed), TransactionTrace: convertTxnTrace(txnResult.Trace), - UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed), + UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed, simplify), } if !txnResult.FixedSigner.IsZero() { @@ -479,10 +479,13 @@ func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResul return result } -func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker) *model.SimulateUnnamedResourcesAccessed { +func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker, simplify bool) *model.SimulateUnnamedResourcesAccessed { if resources == nil { return nil } + if simplify { + resources.Simplify() + } return &model.SimulateUnnamedResourcesAccessed{ Accounts: sliceOrNil(stringSlice(slices.Collect(maps.Keys(resources.Accounts)))), Assets: sliceOrNil(slices.Collect(maps.Keys(resources.Assets))), @@ -554,10 +557,10 @@ func convertSimulateInitialStates(initialStates *simulation.ResourcesInitialStat } } -func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult { +func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult, simplify bool) PreEncodedSimulateTxnGroupResult { txnResults := make([]PreEncodedSimulateTxnResult, len(txnGroupResult.Txns)) for i, txnResult := range txnGroupResult.Txns { - txnResults[i] = convertTxnResult(txnResult) + txnResults[i] = convertTxnResult(txnResult, simplify) } encoded := PreEncodedSimulateTxnGroupResult{ @@ -565,7 +568,7 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedS FailureMessage: omitEmpty(txnGroupResult.FailureMessage), AppBudgetAdded: omitEmpty(txnGroupResult.AppBudgetAdded), AppBudgetConsumed: omitEmpty(txnGroupResult.AppBudgetConsumed), - UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed), + UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed, simplify), } if len(txnGroupResult.FailedAt) > 0 { @@ -576,7 +579,7 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedS return encoded } -func convertSimulationResult(result simulation.Result) PreEncodedSimulateResponse { +func convertSimulationResult(result simulation.Result, simplify bool) PreEncodedSimulateResponse { var evalOverrides *model.SimulationEvalOverrides if result.EvalOverrides != (simulation.ResultEvalOverrides{}) { evalOverrides = &model.SimulationEvalOverrides{ @@ -590,9 +593,11 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons } return PreEncodedSimulateResponse{ - Version: result.Version, - LastRound: result.LastRound, - TxnGroups: util.Map(result.TxnGroups, convertTxnGroupResult), + Version: result.Version, + LastRound: result.LastRound, + TxnGroups: util.Map(result.TxnGroups, func(tg simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult { + return convertTxnGroupResult(tg, simplify) + }), EvalOverrides: evalOverrides, ExecTraceConfig: result.TraceConfig, InitialStates: convertSimulateInitialStates(result.InitialStates), diff --git a/data/basics/overflow.go b/data/basics/overflow.go index a277c276f3..17ae0b6769 100644 --- a/data/basics/overflow.go +++ b/data/basics/overflow.go @@ -17,6 +17,7 @@ package basics import ( + "math" "math/bits" "golang.org/x/exp/constraints" @@ -41,6 +42,22 @@ func OSub[T constraints.Unsigned](a, b T) (res T, overflowed bool) { return } +// ODiff should be used when you really do want the signed difference between +// uint64s, but still care about detecting overflow. I don't _think_ it can be +// generic to different bit widths. +func ODiff(a, b uint64) (res int64, overflowed bool) { + if a >= b { + if a-b > math.MaxInt64 { + return 0, true + } + return int64(a - b), false + } + if b-a > uint64(math.MaxInt64)+1 { + return 0, true + } + return -int64(b - a), false +} + // OMul multiplies 2 values with overflow detection func OMul[T constraints.Unsigned](a, b T) (res T, overflowed bool) { if b == 0 { diff --git a/data/basics/units_test.go b/data/basics/units_test.go index 93afd8ecb2..6ea36f2b05 100644 --- a/data/basics/units_test.go +++ b/data/basics/units_test.go @@ -22,9 +22,45 @@ import ( "testing" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestODiff(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cases := []struct { + a, b uint64 + diff int64 + o bool + }{ + {10, 8, 2, false}, + {10, 0, 10, false}, + {10, 80, -70, false}, + {0, 20, -20, false}, + + {math.MaxInt64 + 1, 0, 0, true}, + {math.MaxInt64, 0, math.MaxInt64, false}, + + {uint64(math.MaxInt64) + 2, 1, 0, true}, + {uint64(math.MaxInt64) + 2, 2, math.MaxInt64, false}, + + // Since minint has higher absolute value than maxint, no overflow here + {1, uint64(math.MaxInt64) + 2, math.MinInt64, false}, + {2, uint64(math.MaxInt64) + 2, math.MinInt64 + 1, false}, + + {math.MaxInt64 + 200, math.MaxInt64, 200, false}, + } + + for i, c := range cases { + diff, o := ODiff(c.a, c.b) + assert.Equal(t, c.diff, diff, + "#%d) %v - %v was %v, not %v", i, c.a, c.b, diff, c.diff) + assert.Equal(t, c.o, o, i) + } +} + func TestSubSaturate(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index 3a85951292..a4e7845ff0 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -45,20 +45,45 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS } dirty, ok := cx.available.boxes[basics.BoxRef{App: cx.appID, Name: name}] + + newAppAccess := false + // maybe allow it (and account for it) if a newly created app is accessing a + // box. we allow this because we know the box is empty upon first touch, so + // we don't have to go to the disk. but we only allow one such access for + // each spare (empty) box ref. that way, we can't end up needing to write + // many separate newly created boxes. + if !ok && cx.Proto.EnableUnnamedBoxAccessInNewApps { + if _, newAppAccess = cx.available.createdApps[cx.appID]; newAppAccess { + if cx.available.unnamedAccess > 0 { + ok = true // allow it + cx.available.unnamedAccess-- // account for it + dirty = false // no-op, but for clarity + + // it will be marked dirty and dirtyBytes will be incremented + // below, like any create. as a (good) side-effect it will go + // into `cx.available` so that later uses will see it in + // available.boxes, skipping this section + } + } + } + if !ok && cx.UnnamedResources != nil { - ok = cx.UnnamedResources.AvailableBox(cx.appID, name, operation, createSize) + ok = cx.UnnamedResources.AvailableBox(cx.appID, name, newAppAccess, createSize) } if !ok { return nil, false, fmt.Errorf("invalid Box reference %#x", name) } - // Since the box is in cx.available, we know this GetBox call is cheap. It - // will go (at most) to the cowRoundBase. Knowledge about existence - // simplifies write budget tracking, then we return the info to avoid yet - // another call to GetBox which most ops need anyway. - content, exists, err := cx.Ledger.GetBox(cx.appID, name) - if err != nil { - return nil, false, err + // If the box is in cx.available, GetBox() is cheap. It will go (at most) to + // the cowRoundBase. But if we did a "newAppAccess", GetBox would go to disk + // just to find the box is not there. So we skip it. + content, exists := []byte(nil), false + if !newAppAccess { + var getErr error + content, exists, getErr = cx.Ledger.GetBox(cx.appID, name) + if getErr != nil { + return nil, false, getErr + } } switch operation { @@ -69,8 +94,9 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS } // Since it exists, we have no dirty work to do. The weird case of // box_put, which seems like a combination of create and write, is - // properly handled because already used boxWrite to declare the - // intent to write (and track dirtiness). + // properly handled because opBoxPut uses BoxWriteOperation to + // declare the intent to write (and track dirtiness). opBoxPut + // performs the length match check itself. return content, exists, nil } fallthrough // If it doesn't exist, a create is like write @@ -106,7 +132,7 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS return content, exists, nil } -func argCheck(cx *EvalContext, name string, size uint64) error { +func lengthChecks(cx *EvalContext, name string, size uint64) error { // Enforce length rules. Currently these are the same as enforced by // ledger. If these were ever to change in proto, we would need to isolate // changes to different program versions. (so a v7 app could not see a @@ -130,7 +156,7 @@ func opBoxCreate(cx *EvalContext) error { name := string(cx.Stack[prev].Bytes) size := cx.Stack[last].Uint - err := argCheck(cx, name, size) + err := lengthChecks(cx, name, size) if err != nil { return err } @@ -160,7 +186,7 @@ func opBoxExtract(cx *EvalContext) error { start := cx.Stack[prev].Uint length := cx.Stack[last].Uint - err := argCheck(cx, name, basics.AddSaturate(start, length)) + err := lengthChecks(cx, name, basics.AddSaturate(start, length)) if err != nil { return err } @@ -187,7 +213,7 @@ func opBoxReplace(cx *EvalContext) error { start := cx.Stack[prev].Uint name := string(cx.Stack[pprev].Bytes) - err := argCheck(cx, name, basics.AddSaturate(start, uint64(len(replacement)))) + err := lengthChecks(cx, name, basics.AddSaturate(start, uint64(len(replacement)))) if err != nil { return err } @@ -215,7 +241,7 @@ func opBoxSplice(cx *EvalContext) error { start := cx.Stack[last-2].Uint name := string(cx.Stack[last-3].Bytes) - err := argCheck(cx, name, 0) + err := lengthChecks(cx, name, 0) if err != nil { return err } @@ -240,7 +266,7 @@ func opBoxDel(cx *EvalContext) error { last := len(cx.Stack) - 1 // name name := string(cx.Stack[last].Bytes) - err := argCheck(cx, name, 0) + err := lengthChecks(cx, name, 0) if err != nil { return err } @@ -266,7 +292,7 @@ func opBoxResize(cx *EvalContext) error { name := string(cx.Stack[prev].Bytes) size := cx.Stack[last].Uint - err := argCheck(cx, name, size) + err := lengthChecks(cx, name, size) if err != nil { return err } @@ -305,7 +331,7 @@ func opBoxLen(cx *EvalContext) error { last := len(cx.Stack) - 1 // name name := string(cx.Stack[last].Bytes) - err := argCheck(cx, name, 0) + err := lengthChecks(cx, name, 0) if err != nil { return err } @@ -323,7 +349,7 @@ func opBoxGet(cx *EvalContext) error { last := len(cx.Stack) - 1 // name name := string(cx.Stack[last].Bytes) - err := argCheck(cx, name, 0) + err := lengthChecks(cx, name, 0) if err != nil { return err } @@ -346,7 +372,7 @@ func opBoxPut(cx *EvalContext) error { value := cx.Stack[last].Bytes name := string(cx.Stack[prev].Bytes) - err := argCheck(cx, name, uint64(len(value))) + err := lengthChecks(cx, name, uint64(len(value))) if err != nil { return err } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f29b2b1a03..d8b30228c7 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -284,7 +284,8 @@ type UnnamedResourcePolicy interface { AvailableApp(app basics.AppIndex) bool AllowsHolding(addr basics.Address, asset basics.AssetIndex) bool AllowsLocal(addr basics.Address, app basics.AppIndex) bool - AvailableBox(app basics.AppIndex, name string, operation BoxOperation, createSize uint64) bool + AvailableBox(app basics.AppIndex, name string, newAppAccess bool, createSize uint64) bool + IOSurplus(surplus int64) bool } // EvalConstants contains constant parameters that are used by opcodes during evaluation (including both real-execution and simulation). @@ -363,10 +364,13 @@ type EvalParams struct { // readBudgetChecked allows us to only check the read budget once readBudgetChecked bool - // SurplusReadBudget is the number of bytes from the IO budget that were not used for reading - // in boxes before evaluation began. In other words, the txn group could have read in - // SurplusReadBudget more box bytes, but did not. - SurplusReadBudget uint64 + // SurplusReadBudget is the number of bytes from the IO budget that were not + // used for reading in boxes before evaluation began. In other words, the + // txn group could have read in SurplusReadBudget more box bytes, but did + // not. It is signed because `simulate` evaluates groups even if they come + // in with insufficient io budget, and reports the need, when invoked with + // AllowUnnamedResources. + SurplusReadBudget int64 EvalConstants @@ -1122,11 +1126,17 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam // If this is a creation... if cx.txn.Txn.ApplicationID == 0 { // make any "0 index" box refs available now that we have an appID. + // This allows case 2b in TestNewAppBoxCreate of boxtxn_test.go for _, br := range cx.txn.Txn.Boxes { if br.Index == 0 { cx.EvalParams.available.boxes[basics.BoxRef{App: cx.appID, Name: string(br.Name)}] = false } } + for _, rr := range cx.txn.Txn.Access { + if len(rr.Box.Name) > 0 && rr.Box.Index == 0 { // len check ensures we have a box ref + cx.EvalParams.available.boxes[basics.BoxRef{App: cx.appID, Name: string(rr.Box.Name)}] = false + } + } // and add the appID to `createdApps` if cx.EvalParams.available.createdApps == nil { cx.EvalParams.available.createdApps = make(map[basics.AppIndex]struct{}) @@ -1146,9 +1156,11 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } } } - cx.ioBudget = bumps * cx.Proto.BytesPerBoxReference + cx.ioBudget = basics.MulSaturate(bumps, cx.Proto.BytesPerBoxReference) used := uint64(0) + var surplus int64 + var overflow bool for br := range cx.available.boxes { if len(br.Name) == 0 { // 0 length names are not allowed for actual created boxes, but @@ -1166,7 +1178,9 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam cx.available.boxes[br] = false used = basics.AddSaturate(used, size) - if used > cx.ioBudget { + surplus, overflow = basics.ODiff(cx.ioBudget, used) + // we defer the check if we have cx.UnnamedResources, so we can ask for the entire surplus at the end. + if overflow || (surplus < 0 && cx.UnnamedResources == nil) { err = fmt.Errorf("box read budget (%d) exceeded", cx.ioBudget) if !cx.Proto.EnableBareBudgetError { // We return an EvalError here because we used to do @@ -1180,8 +1194,14 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam return false, nil, err } } + + // Report the surplus/deficit to the policy, and find out if we should continue + if cx.UnnamedResources != nil && !cx.UnnamedResources.IOSurplus(surplus) { + return false, nil, fmt.Errorf("box read budget (%d) exceeded despite policy", cx.ioBudget) + } + cx.readBudgetChecked = true - cx.SurplusReadBudget = cx.ioBudget - used + cx.SurplusReadBudget = surplus // Can be negative, but only in `simulate` } if cx.Trace != nil && cx.caller != nil { diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 40d380c36c..40251e7551 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2875,10 +2875,10 @@ func allowsLocalEvent(addr basics.Address, aid basics.AppIndex) unnamedResourceP } } -func availableBoxEvent(app basics.AppIndex, name string, operation BoxOperation, createSize uint64) unnamedResourcePolicyEvent { +func availableBoxEvent(app basics.AppIndex, name string, newApp bool, createSize uint64) unnamedResourcePolicyEvent { return unnamedResourcePolicyEvent{ eventType: "AvailableBox", - args: []interface{}{app, name, operation, createSize}, + args: []interface{}{app, name, newApp, createSize}, } } @@ -2919,11 +2919,17 @@ func (p *mockUnnamedResourcePolicy) AllowsLocal(addr basics.Address, aid basics. return p.allowEverything } -func (p *mockUnnamedResourcePolicy) AvailableBox(app basics.AppIndex, name string, operation BoxOperation, createSize uint64) bool { - p.events = append(p.events, availableBoxEvent(app, name, operation, createSize)) +func (p *mockUnnamedResourcePolicy) AvailableBox(app basics.AppIndex, name string, newApp bool, createSize uint64) bool { + p.events = append(p.events, availableBoxEvent(app, name, newApp, createSize)) return p.allowEverything } +// If IOSurplus fails, then everything would fail before the "real" issue being +// tested. So we just pass this in the mock. +func (p *mockUnnamedResourcePolicy) IOSurplus(size int64) bool { + return true +} + func TestUnnamedResourceAccess(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -3125,7 +3131,7 @@ func TestUnnamedResourceAccess(t *testing.T) { if tc.allowsUnnamedResources { testApp(t, source, ep) if tc.policy != nil { - expectedEvents := []unnamedResourcePolicyEvent{availableBoxEvent(tx.ApplicationID, "box key", BoxReadOperation, 0)} + expectedEvents := []unnamedResourcePolicyEvent{availableBoxEvent(tx.ApplicationID, "box key", false, 0)} assert.Equal(t, expectedEvents, tc.policy.events) tc.policy.events = nil } @@ -3136,7 +3142,7 @@ func TestUnnamedResourceAccess(t *testing.T) { if tc.allowsUnnamedResources { testApp(t, source, ep) if tc.policy != nil { - expectedEvents := []unnamedResourcePolicyEvent{availableBoxEvent(tx.ApplicationID, "new box", BoxCreateOperation, 1)} + expectedEvents := []unnamedResourcePolicyEvent{availableBoxEvent(tx.ApplicationID, "new box", false, 1)} assert.Equal(t, expectedEvents, tc.policy.events) tc.policy.events = nil } diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index 7eb3435746..39204daaf3 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -55,6 +55,10 @@ type resources struct { // operation. boxes map[basics.BoxRef]bool + // unnamedAccess is the number of times that a newly created app may access + // a box that was not named. It is decremented for each box accessed this way. + unnamedAccess int + // dirtyBytes maintains a running count of the number of dirty bytes in `boxes` dirtyBytes uint64 } @@ -325,6 +329,11 @@ func (r *resources) fillApplicationCallAccess(ep *EvalParams, hdr *transactions. // ApplicationCallTxnFields.wellFormed ensures no error here. app, name, _ := rr.Box.Resolve(tx.Access) r.shareBox(basics.BoxRef{App: app, Name: name}, tx.ApplicationID) + default: + // all empty equals an "empty boxref" which allows one unnamed access + if ep.Proto.EnableUnnamedBoxAccessInNewApps { + r.unnamedAccess++ + } } } } @@ -365,9 +374,12 @@ func (r *resources) fillApplicationCallForeign(ep *EvalParams, hdr *transactions } for _, br := range tx.Boxes { - app := basics.AppIndex(0) // 0 can be handled by shareBox as current - if br.Index != 0 { - // Upper bounds check will already have been done by + if ep.Proto.EnableUnnamedBoxAccessInNewApps && br.Empty() { + r.unnamedAccess++ + } + var app basics.AppIndex + if br.Index > 0 { + // Bounds check will already have been done by // WellFormed. For testing purposes, it's better to panic // now than after returning a nil. app = tx.ForeignApps[br.Index-1] // shift for the 0=current convention diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index 93f228d596..1d52e3c888 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" @@ -127,8 +128,12 @@ var passThruSource = main(` itxn_submit `) -const boxVersion = 36 -const boxQuotaBumpVersion = 41 +const ( + boxVersion = 36 + accessVersion = 38 + boxQuotaBumpVersion = 41 + newAppCreateVersion = 41 +) func boxFee(p config.ConsensusParams, nameAndValueSize uint64) uint64 { return p.BoxFlatMinBalance + p.BoxByteMinBalance*(nameAndValueSize) @@ -697,3 +702,169 @@ func TestBoxInners(t *testing.T) { dl.txn(call.Args("check", "x", "mark d")) }) } + +// Create the app with bytecode in txn.ApplicationArgs[0], pass my arg[1] as created arg[0] +var passThruCreator = main(` + itxn_begin + txn TypeEnum; itxn_field TypeEnum + txn ApplicationArgs 0; itxn_field ApprovalProgram + txn ApplicationArgs 0; itxn_field ClearStateProgram // need something, won't be used + txn ApplicationArgs 1; itxn_field ApplicationArgs + itxn_submit +`) + +// TestNewAppBoxCreate exercised proto.EnableUnnamedBoxCreate +func TestNewAppBoxCreate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // We're going to create an app that will, during its own creation, + // create a box. That requires two tricks. + + // 1) Figure out the appID it will have and prefund it. This _could_ be + // done within the group itself - an early transaction deduces the + // transaction counter, so it can know what the later create will be, + // and compute it's app address. + + // 2) a) Use the the predicted appID to name the box ref. + // or b) Use 0 as the app in the box ref, meaning "this app" + // or c) EnableUnnamedBoxCreate will allow such a creation if there are empty box refs. + + // 2a is pretty much impossible in practice, we can only do it here + // because our blockchain is "quiet" we know the upcoming appID. + + // 2b won't work for inner app creates, since the 0 appID gets replaced + // with the _top-level_ app's ID. + + // 2c would allow newly created apps, even if inners, to create boxes. + // It also has the nice property of not needing to know the box's + // name. So it can be computed in app. We don't need the name because + // we can short-circuit the lookup - the box _must_ be empty. + + // boxCreate is an app that tries to make a box from its first argument, even during its own creation. + boxCreate := "txn ApplicationArgs 0; int 24; box_create;" // Succeeds if the box is created and did not already exist + + // boxPut is an app that tries to make a box from its first argument, even during its own creation (but using box_put) + boxPut := "txn ApplicationArgs 0; int 24; bzero; box_put; int 1;" + + for _, createSrc := range []string{boxCreate, boxPut} { + // doubleSrc tries to create TWO boxes. The second is always named by ApplicationArgs 1 + doubleSrc := createSrc + `txn ApplicationArgs 1; int 24; box_create; pop;` // return result of FIRST box_create + // need to call one inner txn, and have have mbr for itself and inner created app + passID := dl.fundedApp(addrs[0], 201_000, passThruCreator) // Will be used to show inners have same power + + // Since we used fundedApp, the next app created would be passID+2. + // We'll prefund a whole bunch of the next apps that we can then create + // at will below. + + var testTxns = basics.AppIndex(20) + for i := range testTxns { + dl.txn(&txntest.Txn{Type: "pay", Sender: addrs[0], Receiver: (passID + 2 + testTxns + i).Address(), Amount: 500_000}) + } + + // Try to create it. It will fail because there's no box ref. (does not increment txncounter) + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}}, + "invalid Box reference 0x01") + + // 2a. Create it with a box ref of the predicted appID + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + ForeignApps: []basics.AppIndex{passID + testTxns + 2}, + Boxes: []transactions.BoxRef{{Index: 1, Name: []byte{0x01}}}}) + + // 2a. Create it with a box ref of the predicted appID (Access list) + if ver >= accessVersion { + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + Access: []transactions.ResourceRef{ + {App: passID + testTxns + 3}, + {Box: transactions.BoxRef{Index: 1, Name: []byte{0x01}}}}}) + } + + // 2b. Create it with a box ref of 0, which means "this app" + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte{0x01}}}}) + + // 2b. Create it with a box ref of 0, which means "this app" (Access List) + if ver >= accessVersion { + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + Access: []transactions.ResourceRef{ + {Box: transactions.BoxRef{Index: 0, Name: []byte{0x01}}}}}) + } + + // you can manipulate it twice if you want (this tries to create it twice) + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x01}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte{0x01}}}}) + + // but you still can't make a second box + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte{0x01}}}}, + "invalid Box reference 0x02") + + // until you list it as well + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, + Boxes: []transactions.BoxRef{ + {Index: 0, Name: []byte{0x01}}, + {Index: 0, Name: []byte{0x02}}, + }}) + + if ver >= newAppCreateVersion { + // 2c. Create it with an empty box ref + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + Boxes: []transactions.BoxRef{{}}}) + + // 2c. Create it with an empty box ref + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + Access: []transactions.ResourceRef{{Box: transactions.BoxRef{}}}}) + + // but you can't do a second create + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, + Boxes: []transactions.BoxRef{{}}}, + "invalid Box reference 0x02") + + // until you add a second box ref + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, + Boxes: []transactions.BoxRef{{}, {}}}) + + // Now confirm that 2c also works for an inner created app + ops, err := logic.AssembleString("#pragma version 12\n" + createSrc) + require.NoError(t, err, ops.Errors) + createSrcByteCode := ops.Program + // create app as an inner, fails w/o empty box ref + dl.txn(&txntest.Txn{Sender: addrs[0], + Type: "appl", + ApplicationID: passID, + ApplicationArgs: [][]byte{createSrcByteCode, {0x01}}, + }, "invalid Box reference 0x01") + // create app as an inner, succeeds w/ empty box ref + dl.txn(&txntest.Txn{Sender: addrs[0], + Type: "appl", + ApplicationID: passID, + ApplicationArgs: [][]byte{createSrcByteCode, {0x01}}, + Boxes: []transactions.BoxRef{{}}, + }) + } else { + // 2c. Doesn't work yet until `newAppCreateVersion` + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrc, ApplicationArgs: [][]byte{{0x01}}, + Boxes: []transactions.BoxRef{{}}}, + "invalid Box reference 0x01") + } + } + }) +} diff --git a/ledger/simulation/resources.go b/ledger/simulation/resources.go index ae255c5e8d..9394bd8fbd 100644 --- a/ledger/simulation/resources.go +++ b/ledger/simulation/resources.go @@ -31,22 +31,47 @@ import ( // ResourceTracker calculates the additional resources that a transaction or group could use, and // it tracks any referenced unnamed resources that fit within those limits. type ResourceTracker struct { - Accounts map[basics.Address]struct{} + Accounts map[basics.Address]struct{} + // MaxAccounts is the largest number of new accounts that could possibly be + // referenced by the thing being tracked (txn or group) beyond what is + // already listed. MaxAccounts int - Assets map[basics.AssetIndex]struct{} + Assets map[basics.AssetIndex]struct{} + // MaxAssets is the largest number of new assets that could possibly be + // referenced by the thing being tracked (txn or group) beyond what is + // already listed. MaxAssets int - Apps map[basics.AppIndex]struct{} + Apps map[basics.AppIndex]struct{} + // MaxApps is the largest number of new apps that could possibly be + // referenced by the thing being tracked (txn or group) beyond what is + // already listed. MaxApps int // The map value is the size of the box loaded from the ledger prior to any writes. This is used // to track the box read budget. - Boxes map[basics.BoxRef]uint64 - MaxBoxes int + Boxes map[basics.BoxRef]BoxStat + MaxBoxes int + + // NumEmptyBoxRefs tracks the number of additional BoxRefs that will be + // required to handle the boxes this tracker has observed (for i/o budget, + // or to allow access to boxes in new apps). NumEmptyBoxRefs int - maxWriteBudget uint64 + // maxWriteBudget is the "high-water mark" for dirty box writes. A group + // must have enough write budget during its entire execution to accommodate + // writing its dirty boxes. This is maintained after each opcode. + maxWriteBudget uint64 + + // initialReadSurplus is the I/O surplus available based on the submitted + // group, so it takes into account the number of box refs supplied, and the + // size of the boxes named. It can be negative! + initialReadSurplus int64 + + // MaxTotalRefs is the largest number of new refs of any kind that could + // possibly be added tp the thing being tracked (txn or group) beyond what + // it began with. MaxTotalRefs int AssetHoldings map[ledgercore.AccountAsset]struct{} @@ -54,6 +79,12 @@ type ResourceTracker struct { MaxCrossProductReferences int } +// BoxStat is what needs to be tracked in order to figure out need resources (especially empty refs) +type BoxStat struct { + ReadSize uint64 // how much read quota did this access consume? + NewApp bool // was the box accessed by an app created in this group? +} + func makeTxnResourceTracker(txn *transactions.Transaction, proto *config.ConsensusParams) ResourceTracker { if txn.Type != protocol.ApplicationCallTx { return ResourceTracker{} @@ -70,6 +101,9 @@ func makeTxnResourceTracker(txn *transactions.Transaction, proto *config.Consens } } +// makeGlobalResourceTracker populates a tracker so that it knows what are the +// bounds on how many new references of various types that could potentially be +// allowed for the group. func makeGlobalResourceTracker(perTxnResources []ResourceTracker, nonAppCalls int, proto *config.ConsensusParams) ResourceTracker { // Calculate the maximum number of cross-product resources that can be accessed by one app call // under normal circumstances. This is calculated using the case of an app call with a full set @@ -107,17 +141,27 @@ func makeGlobalResourceTracker(perTxnResources []ResourceTracker, nonAppCalls in func (a *ResourceTracker) removePrivateFields() { a.maxWriteBudget = 0 + a.initialReadSurplus = 0 } // HasResources returns true if the tracker has any resources. -func (a *ResourceTracker) HasResources() bool { - return len(a.Accounts) != 0 || len(a.Assets) != 0 || len(a.Apps) != 0 || len(a.Boxes) != 0 || len(a.AssetHoldings) != 0 || len(a.AppLocals) != 0 +func (a ResourceTracker) HasResources() bool { + return len(a.Accounts) != 0 || len(a.Assets) != 0 || len(a.Apps) != 0 || len(a.Boxes) != 0 || len(a.AssetHoldings) != 0 || len(a.AppLocals) != 0 || a.NumEmptyBoxRefs != 0 +} + +// refCount returns the number of references this tracker has. +func (a ResourceTracker) refCount() int { + return len(a.Accounts) + len(a.Assets) + len(a.Apps) + a.boxCount() +} + +// boxCount returns the number of box references this tracker has. +func (a ResourceTracker) boxCount() int { + return len(a.Boxes) + a.NumEmptyBoxRefs } func (a *ResourceTracker) hasAccount(addr basics.Address, ep *logic.EvalParams, programVersion uint64) bool { // nil map lookup is ok - _, ok := a.Accounts[addr] - if ok { + if _, ok := a.Accounts[addr]; ok { return true } if programVersion >= 7 { // appAddressAvailableVersion @@ -131,7 +175,7 @@ func (a *ResourceTracker) hasAccount(addr basics.Address, ep *logic.EvalParams, } func (a *ResourceTracker) addAccount(addr basics.Address) bool { - if len(a.Accounts) >= a.MaxAccounts || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + if len(a.Accounts) >= a.MaxAccounts || a.refCount() >= a.MaxTotalRefs { return false } if a.Accounts == nil { @@ -141,8 +185,11 @@ func (a *ResourceTracker) addAccount(addr basics.Address) bool { return true } +// removeAccountSlot notes that there is one less account slot available for +// referencing new assets. It's used to tell the global tracker that a local +// slot is used. func (a *ResourceTracker) removeAccountSlot() bool { - if len(a.Accounts) >= a.MaxAccounts || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + if len(a.Accounts) >= a.MaxAccounts || a.refCount() >= a.MaxTotalRefs { return false } a.MaxAccounts-- @@ -150,14 +197,16 @@ func (a *ResourceTracker) removeAccountSlot() bool { return true } -func (a *ResourceTracker) hasAsset(aid basics.AssetIndex) bool { +func (a ResourceTracker) hasAsset(aid basics.AssetIndex) bool { // nil map lookup is ok _, ok := a.Assets[aid] return ok } +// addAsset records that an asset reference must be added. It return false if +// that is known to be impossible. func (a *ResourceTracker) addAsset(aid basics.AssetIndex) bool { - if len(a.Assets) >= a.MaxAssets || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + if len(a.Assets) >= a.MaxAssets || a.refCount() >= a.MaxTotalRefs { return false } if a.Assets == nil { @@ -167,8 +216,11 @@ func (a *ResourceTracker) addAsset(aid basics.AssetIndex) bool { return true } +// removeAssetSlot notes that there is one less asset slot available for +// referencing new assets. It's used to tell the global tracker that a local +// slot is used. func (a *ResourceTracker) removeAssetSlot() bool { - if len(a.Assets) >= a.MaxAssets || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + if len(a.Assets) >= a.MaxAssets || a.refCount() >= a.MaxTotalRefs { return false } a.MaxAssets-- @@ -176,7 +228,7 @@ func (a *ResourceTracker) removeAssetSlot() bool { return true } -func (a *ResourceTracker) hasApp(aid basics.AppIndex) bool { +func (a ResourceTracker) hasApp(aid basics.AppIndex) bool { // nil map lookup is ok _, ok := a.Apps[aid] return ok @@ -200,7 +252,7 @@ func (a *ResourceTracker) addApp(aid basics.AppIndex, ep *logic.EvalParams, prog } } - if len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + if a.refCount() >= a.MaxTotalRefs { return false } if a.Apps == nil { @@ -211,7 +263,7 @@ func (a *ResourceTracker) addApp(aid basics.AppIndex, ep *logic.EvalParams, prog } func (a *ResourceTracker) removeAppSlot() bool { - if len(a.Apps) >= a.MaxApps || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + if len(a.Apps) >= a.MaxApps || a.refCount() >= a.MaxTotalRefs { return false } a.MaxApps-- @@ -222,21 +274,26 @@ func (a *ResourceTracker) removeAppSlot() bool { return true } -func (a *ResourceTracker) hasBox(app basics.AppIndex, name string) bool { +func (a ResourceTracker) hasBox(app basics.AppIndex, name string) bool { // nil map lookup is ok _, ok := a.Boxes[basics.BoxRef{App: app, Name: name}] return ok } -func (a *ResourceTracker) addBox(app basics.AppIndex, name string, readSize, additionalReadBudget, bytesPerBoxRef uint64) bool { +func (a *ResourceTracker) addBox(app basics.AppIndex, name string, newApp bool, readSize uint64, bytesPerBoxRef uint64) bool { usedReadBudget := basics.AddSaturate(a.usedBoxReadBudget(), readSize) // Adding bytesPerBoxRef to account for the new IO budget from adding an additional box ref - readBudget := additionalReadBudget + a.boxIOBudget(bytesPerBoxRef) + bytesPerBoxRef + ioBudget := a.boxIOBudget(bytesPerBoxRef) + bytesPerBoxRef + if a.initialReadSurplus > 0 { + ioBudget += uint64(a.initialReadSurplus) + } else { + ioBudget -= uint64(-a.initialReadSurplus) + } var emptyRefs int - if usedReadBudget > readBudget { + if usedReadBudget > ioBudget { // We need to allocate more empty box refs to increase the read budget - neededBudget := usedReadBudget - readBudget + neededBudget := usedReadBudget - ioBudget emptyRefsU64 := basics.DivCeil(neededBudget, bytesPerBoxRef) if emptyRefsU64 > math.MaxInt { // This should never happen, but if we overflow an int with the number of extra pages @@ -244,22 +301,45 @@ func (a *ResourceTracker) addBox(app basics.AppIndex, name string, readSize, add return false } emptyRefs = int(emptyRefsU64) - } else if a.NumEmptyBoxRefs != 0 { - surplusBudget := readBudget - usedReadBudget - if surplusBudget >= bytesPerBoxRef && readBudget-bytesPerBoxRef >= a.maxWriteBudget { - // If we already have enough read budget, remove one empty ref to be replaced by the new - // named box ref. + } else if a.NumEmptyBoxRefs > 0 { // If there are empties added for quota, we may not need as many. + surplusReadBudget := basics.SubSaturate(ioBudget, usedReadBudget) + surplusWriteBudget := basics.SubSaturate(ioBudget, a.maxWriteBudget) + if surplusReadBudget >= bytesPerBoxRef && surplusWriteBudget >= bytesPerBoxRef { + // By adding this box, we may no longer need an empty ref we previously added for quota. emptyRefs = -1 } } - if emptyRefs >= a.MaxBoxes-len(a.Boxes)-a.NumEmptyBoxRefs || emptyRefs >= a.MaxTotalRefs-len(a.Accounts)-len(a.Assets)-len(a.Apps)-len(a.Boxes)-a.NumEmptyBoxRefs { + if emptyRefs >= a.MaxBoxes-a.boxCount() || emptyRefs >= a.MaxTotalRefs-a.refCount() { return false } if a.Boxes == nil { - a.Boxes = make(map[basics.BoxRef]uint64) + a.Boxes = make(map[basics.BoxRef]BoxStat) + } + a.Boxes[basics.BoxRef{App: app, Name: name}] = BoxStat{readSize, newApp} + a.NumEmptyBoxRefs += emptyRefs + return true +} + +// ioSurplus notes whether extra bytes can be read because of the initially +// supplied box refs. If not, it ensures that empty box refs can be added to +// account for the deficit. +func (a *ResourceTracker) ioSurplus(amount int64, bytesPerBoxRef uint64) bool { + a.initialReadSurplus = amount + if amount > 0 { + return true + } + neededBudget := uint64(-amount) + emptyRefsU64 := basics.DivCeil(neededBudget, bytesPerBoxRef) + if emptyRefsU64 > math.MaxInt { + // This should never happen, but if we overflow an int with the number of extra pages + // needed, we can't support this request. + return false + } + emptyRefs := int(emptyRefsU64) + if emptyRefs >= a.MaxBoxes-a.boxCount() || emptyRefs >= a.MaxTotalRefs-a.refCount() { + return false } - a.Boxes[basics.BoxRef{App: app, Name: name}] = readSize a.NumEmptyBoxRefs += emptyRefs return true } @@ -276,7 +356,7 @@ func (a *ResourceTracker) addEmptyBoxRefsForWriteBudget(usedWriteBudget, additio return false } extraRefs := int(extraRefsU64) - if extraRefs > a.MaxBoxes-len(a.Boxes)-a.NumEmptyBoxRefs || extraRefs > a.MaxTotalRefs-len(a.Accounts)-len(a.Assets)-len(a.Apps)-len(a.Boxes)-a.NumEmptyBoxRefs { + if extraRefs > a.MaxBoxes-a.boxCount() || extraRefs > a.MaxTotalRefs-a.refCount() { return false } a.NumEmptyBoxRefs += extraRefs @@ -287,24 +367,20 @@ func (a *ResourceTracker) addEmptyBoxRefsForWriteBudget(usedWriteBudget, additio return true } -func (a *ResourceTracker) boxIOBudget(bytesPerBoxRef uint64) uint64 { - return uint64(len(a.Boxes)+a.NumEmptyBoxRefs) * bytesPerBoxRef +func (a ResourceTracker) boxIOBudget(bytesPerBoxRef uint64) uint64 { + return uint64(a.boxCount()) * bytesPerBoxRef } func (a *ResourceTracker) usedBoxReadBudget() uint64 { var budget uint64 - for _, readSize := range a.Boxes { - budget += readSize + for _, bs := range a.Boxes { + budget += bs.ReadSize } return budget } func (a *ResourceTracker) maxPossibleUnnamedBoxes() int { - numBoxes := a.MaxTotalRefs - len(a.Accounts) - len(a.Assets) - len(a.Apps) - if a.MaxBoxes < numBoxes { - numBoxes = a.MaxBoxes - } - return numBoxes + return min(a.MaxBoxes, a.MaxTotalRefs-len(a.Accounts)-len(a.Assets)-len(a.Apps)) } func (a *ResourceTracker) hasHolding(addr basics.Address, aid basics.AssetIndex) bool { @@ -345,6 +421,22 @@ func (a *ResourceTracker) addLocal(addr basics.Address, aid basics.AppIndex) boo return true } +// Simplify makes the ResourceTracker easier to consume for callers. It avoids +// returning boxes "named" by a newly created app. Those app IDs would not be +// usable in a later submission. An empty ref can stand in. Once Simplified, the +// tracker should not be used to for further tracking. +func (a *ResourceTracker) Simplify() { + for name, stat := range a.Boxes { + if stat.NewApp { + a.NumEmptyBoxRefs++ + delete(a.Boxes, name) + } + } + if len(a.Boxes) == 0 { + a.Boxes = nil + } +} + // groupResourceTracker calculates the additional resources that a transaction group could use, // and it tracks any referenced unnamed resources that fit within those limits. type groupResourceTracker struct { @@ -356,6 +448,7 @@ type groupResourceTracker struct { // sharing was added). localTxnResources []ResourceTracker + // startingBoxes is the total number of box references in the group, as submitted startingBoxes int } @@ -490,9 +583,14 @@ func (a *groupResourceTracker) hasBox(app basics.AppIndex, name string) bool { return a.globalResources.hasBox(app, name) } -func (a *groupResourceTracker) addBox(app basics.AppIndex, name string, readSize, additionalReadBudget, bytesPerBoxRef uint64) bool { +func (a *groupResourceTracker) addBox(app basics.AppIndex, name string, newApp bool, readSize uint64, bytesPerBoxRef uint64) bool { + // All boxes are global, never consult localTxnResources + return a.globalResources.addBox(app, name, newApp, readSize, bytesPerBoxRef) +} + +func (a *groupResourceTracker) ioSurplus(readSize int64, bytesPerBoxRef uint64) bool { // All boxes are global, never consult localTxnResources - return a.globalResources.addBox(app, name, readSize, additionalReadBudget, bytesPerBoxRef) + return a.globalResources.ioSurplus(readSize, bytesPerBoxRef) } func (a *groupResourceTracker) reconcileBoxWriteBudget(used uint64, bytesPerBoxRef uint64) error { @@ -532,7 +630,7 @@ func (a *groupResourceTracker) addLocal(addr basics.Address, aid basics.AppIndex type resourcePolicy struct { tracker groupResourceTracker ep *logic.EvalParams - initialBoxSurplusReadBudget *uint64 + initialBoxSurplusReadBudget *int64 txnRootIndex int programVersion uint64 @@ -588,13 +686,14 @@ func (p *resourcePolicy) AllowsLocal(addr basics.Address, aid basics.AppIndex) b return p.tracker.addLocal(addr, aid) } -func (p *resourcePolicy) AvailableBox(app basics.AppIndex, name string, operation logic.BoxOperation, createSize uint64) bool { +func (p *resourcePolicy) AvailableBox(app basics.AppIndex, name string, newApp bool, createSize uint64) bool { if p.tracker.hasBox(app, name) { - // We actually never expect this to happen, since the EvalContext remembers each box in - // order to track their dirty bytes, and it won't invoke this method if it's already seen - // the box. + // We never expect this to happen. The EvalContext remembers each box in + // order to track their dirty bytes, and it won't invoke this method if + // it's already seen the box. return true } + box, ok, err := p.ep.Ledger.GetBox(app, name) if err != nil { panic(err.Error()) @@ -603,5 +702,10 @@ func (p *resourcePolicy) AvailableBox(app basics.AppIndex, name string, operatio if ok { readSize = uint64(len(box)) } - return p.tracker.addBox(app, name, readSize, *p.initialBoxSurplusReadBudget, p.ep.Proto.BytesPerBoxReference) + + return p.tracker.addBox(app, name, newApp, readSize, p.ep.Proto.BytesPerBoxReference) +} + +func (p *resourcePolicy) IOSurplus(size int64) bool { + return p.tracker.ioSurplus(size, p.ep.Proto.BytesPerBoxReference) } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 936a3047c4..bfdc4e7df2 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -149,9 +149,17 @@ func simulationTest(t *testing.T, f func(env simulationtesting.Environment) simu } func runSimulationTestCase(t *testing.T, env simulationtesting.Environment, testcase simulationTestCase) { + t.Helper() + actual, err := simulation.MakeSimulator(env.Ledger, testcase.developerAPI).Simulate(testcase.input) require.NoError(t, err) + for i := range actual.TxnGroups { + if actual.TxnGroups[i].UnnamedResourcesAccessed != nil { + actual.TxnGroups[i].UnnamedResourcesAccessed.Simplify() + } + } + validateSimulationResult(t, actual) require.Len(t, testcase.expected.TxnGroups, len(testcase.input.TxnGroups), "Test case must expect the same number of transaction groups as its input") @@ -7067,9 +7075,9 @@ func TestUnnamedResources(t *testing.T) { if v >= 8 { // boxes introduced program += `byte "A"; int 64; box_create; assert;` program += `byte "B"; box_len; !; assert; !; assert;` - expectedUnnamedResourceGroupAssignment.Boxes = map[basics.BoxRef]uint64{ - {App: 0, Name: "A"}: 0, - {App: 0, Name: "B"}: 0, + expectedUnnamedResourceGroupAssignment.Boxes = map[basics.BoxRef]simulation.BoxStat{ + {App: 0, Name: "A"}: {}, + {App: 0, Name: "B"}: {}, } } @@ -7517,10 +7525,14 @@ int 1 } } -const boxTestProgram = `#pragma version %d +const verPragma = "#pragma version %d\n" + +const bailOnCreate = ` txn ApplicationID -bz end // Do nothing during create +bz end +` +const mainBoxTestProgram = ` byte "create" byte "delete" byte "read" @@ -7560,12 +7572,22 @@ end: int 1 ` +// boxTestProgram executes the operations defined by boxOperation +const boxTestProgram = verPragma + bailOnCreate + mainBoxTestProgram + +// boxDuringCreateProgram will even try to operate during the app creation. +const boxDuringCreateProgram = verPragma + mainBoxTestProgram + +// boxOperation is used to describe something we want done to a box. A +// transaction doing it will be created and run in a test. type boxOperation struct { op logic.BoxOperation name string createSize uint64 contents []byte otherRefCount int + withBoxRefs int // Add this many box refs to the generated transaction + duringCreate bool // If true, instantiate `boxDuringCreateProgram` to execute the op } func (o boxOperation) appArgs() [][]byte { @@ -7602,13 +7624,16 @@ func (o boxOperation) boxRefs() []transactions.BoxRef { } type boxTestResult struct { - Boxes map[basics.BoxRef]uint64 + Boxes map[basics.BoxRef]uint64 // maps observed boxes to their size when read NumEmptyBoxRefs int FailureMessage string FailingIndex int } +// testUnnamedBoxOperations creates a group with one transaction per boxOp, +// calling `app` with arguments meant to effect the boxOps. The results must +// match `expected`. func testUnnamedBoxOperations(t *testing.T, env simulationtesting.Environment, app basics.AppIndex, boxOps []boxOperation, expected boxTestResult) { t.Helper() @@ -7616,6 +7641,7 @@ func testUnnamedBoxOperations(t *testing.T, env simulationtesting.Environment, a require.LessOrEqual(t, len(boxOps), maxGroupSize) otherAssets := 0 + boxRefs := 0 txns := make([]*txntest.Txn, maxGroupSize) for i, op := range boxOps { txn := env.TxnInfo.NewTxn(txntest.Txn{ @@ -7624,10 +7650,18 @@ func testUnnamedBoxOperations(t *testing.T, env simulationtesting.Environment, a ApplicationID: app, ApplicationArgs: op.appArgs(), ForeignAssets: make([]basics.AssetIndex, op.otherRefCount), + Boxes: slices.Repeat(op.boxRefs(), op.withBoxRefs), Note: []byte{byte(i)}, // Make each txn unique }) + if op.duringCreate { + txn.ApplicationID = 0 + v := env.TxnInfo.CurrentProtocolParams().LogicSigVersion + txn.ApprovalProgram = fmt.Sprintf(boxDuringCreateProgram, v) + txn.ClearStateProgram = fmt.Sprintf("#pragma version %d\n int 1", v) + } txns[i] = &txn otherAssets += op.otherRefCount + boxRefs += op.withBoxRefs } for i := len(boxOps); i < maxGroupSize; i++ { // Fill out the rest of the group with non-app transactions. This reduces the amount of @@ -7649,6 +7683,11 @@ func testUnnamedBoxOperations(t *testing.T, env simulationtesting.Environment, a expectedTxnResults := make([]simulation.TxnResult, len(stxns)) for i := range expectedTxnResults { expectedTxnResults[i].AppBudgetConsumed = ignoreAppBudgetConsumed + if i < len(boxOps) && boxOps[i].duringCreate { + // 1007 here is because of the number of transactions we used to + // setup the env. See explanation in: TestUnnamedResourcesBoxIOBudget + expectedTxnResults[i].Txn.ApplyData.ApplicationID = 1007 + basics.AppIndex(i) + } } var failedAt simulation.TxnPath @@ -7661,14 +7700,19 @@ func testUnnamedBoxOperations(t *testing.T, env simulationtesting.Environment, a MaxAccounts: len(boxOps) * (proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps), MaxAssets: len(boxOps)*proto.MaxAppTxnForeignAssets - otherAssets, MaxApps: len(boxOps) * proto.MaxAppTxnForeignApps, - MaxBoxes: len(boxOps) * proto.MaxAppBoxReferences, - MaxTotalRefs: len(boxOps)*proto.MaxAppTotalTxnReferences - otherAssets, + MaxBoxes: len(boxOps)*proto.MaxAppBoxReferences - boxRefs, + MaxTotalRefs: len(boxOps)*proto.MaxAppTotalTxnReferences - otherAssets - boxRefs, - Boxes: expected.Boxes, NumEmptyBoxRefs: expected.NumEmptyBoxRefs, MaxCrossProductReferences: len(boxOps) * proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), } + if expected.Boxes != nil { + expectedUnnamedResources.Boxes = make(map[basics.BoxRef]simulation.BoxStat, len(expected.Boxes)) + for key, size := range expected.Boxes { + expectedUnnamedResources.Boxes[key] = simulation.BoxStat{ReadSize: size} + } + } if !expectedUnnamedResources.HasResources() { expectedUnnamedResources = nil @@ -7726,8 +7770,12 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { }) // MBR is needed for boxes. - transferable := env.Accounts[1].AcctData.MicroAlgos.Raw - proto.MinBalance - proto.MinTxnFee - env.TransferAlgos(env.Accounts[1].Addr, appID.Address(), transferable) + transferable := env.Accounts[1].AcctData.MicroAlgos.Raw - proto.MinBalance - 2*proto.MinTxnFee + env.TransferAlgos(env.Accounts[1].Addr, appID.Address(), transferable/2) + // we're also going to make new boxes in a new app, which will be + // the sixth txns after the appID creation (because of two + // TrsnaferAlgos and 3 env.Txn, below) + env.TransferAlgos(env.Accounts[1].Addr, (appID + 6).Address(), transferable/2) // Set up boxes A, B, C for testing. // A is a box with a size of exactly BytesPerBoxReference @@ -7776,7 +7824,7 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { // in separate simulations, so we can reuse the same environment and not have to worry // about the effects of one test interfering with another. - // Reading exisitng boxes + // Reading existing boxes testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "A"}, }, boxTestResult{ @@ -7800,6 +7848,12 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { // We need an additional empty box ref because the size of C exceeds BytesPerBoxReference NumEmptyBoxRefs: 1, }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "C", withBoxRefs: 1}, + }, boxTestResult{ + // We need an additional empty box ref because the size of C exceeds BytesPerBoxReference + NumEmptyBoxRefs: 1, + }) testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "A"}, {op: logic.BoxReadOperation, name: "B"}, @@ -7831,6 +7885,8 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { }, // No empty box refs needed because we have perfectly reached 3 * BytesPerBoxReference }) + + // non-existent box testBoxOps([]boxOperation{ {op: logic.BoxReadOperation, name: "Q"}, }, boxTestResult{ @@ -7873,6 +7929,30 @@ func TestUnnamedResourcesBoxIOBudget(t *testing.T) { }, }) + // Try to read during a new app create. These boxes _can't_ exist, so no need for extra read quota + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "X", duringCreate: true}, + }, boxTestResult{ + NumEmptyBoxRefs: 1, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "X", duringCreate: true}, + {op: logic.BoxReadOperation, name: "Y", duringCreate: true}, + }, boxTestResult{ + NumEmptyBoxRefs: 2, + }) + // now try to create, which can cause enough dirty bytes to require empty refs + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "small", createSize: proto.BytesPerBoxReference, duringCreate: true}, + }, boxTestResult{ + NumEmptyBoxRefs: 1, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "big", createSize: proto.BytesPerBoxReference + 1, duringCreate: true}, + }, boxTestResult{ + NumEmptyBoxRefs: 2, + }) + // Creating new boxes and reading existing ones testBoxOps([]boxOperation{ {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, @@ -8506,7 +8586,7 @@ func testUnnamedResourceLimits(t *testing.T, env simulationtesting.Environment, MaxBoxes: proto.MaxAppBoxReferences, MaxTotalRefs: proto.MaxAppTotalTxnReferences, - Boxes: mapWithKeys(boxNamesToRefs(app, resources.boxes()), uint64(0)), + Boxes: mapWithKeys(boxNamesToRefs(app, resources.boxes()), simulation.BoxStat{}), MaxCrossProductReferences: proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), } diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index 36cd0a1d34..27f45a4e13 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -239,7 +239,7 @@ func (env *Environment) Rekey(account, rekeyTo basics.Address) { // PrepareSimulatorTest creates an environment to test transaction simulations. The caller is // responsible for calling Close() on the returned Environment. func PrepareSimulatorTest(t *testing.T) Environment { - genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 100) + genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 200) // Prepare ledger const inMem = true From 242cc03e17aac9f8a14929225c066c1410d36490 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:07:15 -0400 Subject: [PATCH 14/25] tests: debug node TestNodeHybridTopology (#6412) --- node/node_test.go | 75 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index 1cd3c46cb0..0125d628a6 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -114,7 +114,7 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, customConsens } return phonebook } - nodes, wallets := setupFullNodesEx(t, proto, customConsensus, acctStake, configHook, phonebookHook) + nodes, wallets := setupFullNodesEx(t, proto, customConsensus, acctStake, configHook, phonebookHook, &singleFileFullNodeLoggerProvider{t: t}) require.Len(t, nodes, numAccounts) require.Len(t, wallets, numAccounts) return nodes, wallets @@ -123,15 +123,14 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, customConsens func setupFullNodesEx( t *testing.T, proto protocol.ConsensusVersion, customConsensus config.ConsensusProtocols, acctStake []basics.MicroAlgos, configHook configHook, phonebookHook phonebookHook, + lp fullNodeLoggerProvider, ) ([]*AlgorandFullNode, []string) { util.SetFdSoftLimit(1000) - f, _ := os.Create(t.Name() + ".log") - logging.Base().SetJSONFormatter() - logging.Base().SetOutput(f) - logging.Base().SetLevel(logging.Debug) - t.Logf("Logging to %s\n", t.Name()+".log") + if lp == nil { + lp = &singleFileFullNodeLoggerProvider{t: t} + } firstRound := basics.Round(0) lastRound := basics.Round(200) @@ -243,7 +242,7 @@ func setupFullNodesEx( cfg, err := config.LoadConfigFromDisk(rootDirectory) phonebook := phonebookHook(nodeInfos, i) require.NoError(t, err) - node, err := MakeFull(logging.Base().With("net", fmt.Sprintf("node%d", i)), rootDirectory, cfg, phonebook, g) + node, err := MakeFull(lp.getLogger(i), rootDirectory, cfg, phonebook, g) nodes[i] = node require.NoError(t, err) } @@ -251,6 +250,53 @@ func setupFullNodesEx( return nodes, wallets } +// fullNodeLoggerProvider is an interface for providing loggers for full nodes. +type fullNodeLoggerProvider interface { + getLogger(i int) logging.Logger + cleanup() +} + +// singleFileFullNodeLoggerProvider is a logger provider that creates a single log file for all nodes. +type singleFileFullNodeLoggerProvider struct { + t *testing.T + h *os.File + l logging.Logger +} + +func (p *singleFileFullNodeLoggerProvider) getLogger(i int) logging.Logger { + if p.l == nil { + var err error + p.h, err = os.Create(p.t.Name() + ".log") + require.NoError(p.t, err, "Failed to create log file for node %d", i) + p.l = logging.NewLogger() + p.l.SetJSONFormatter() + p.l.SetOutput(p.h) + p.l.SetLevel(logging.Debug) + } + return p.l.With("net", fmt.Sprintf("node%d", i)) +} + +func (p *singleFileFullNodeLoggerProvider) cleanup() { + if p.h != nil { + p.h.Close() + p.h = nil + p.l = nil + } +} + +// mixedLogFullNodeLoggerProvider allows some nodes to log to the testing logger and others to a file. +type mixedLogFullNodeLoggerProvider struct { + singleFileFullNodeLoggerProvider + stdoutNodes map[int]struct{} +} + +func (p *mixedLogFullNodeLoggerProvider) getLogger(i int) logging.Logger { + if _, ok := p.stdoutNodes[i]; ok { + return logging.TestingLog(p.t).With("net", fmt.Sprintf("node%d", i)) + } + return p.singleFileFullNodeLoggerProvider.getLogger(i) +} + func TestSyncingFullNode(t *testing.T) { partitiontest.PartitionTest(t) @@ -941,7 +987,14 @@ func TestNodeHybridTopology(t *testing.T) { return nil } - nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook) + nodes, wallets := setupFullNodesEx( + t, consensusTest0, configurableConsensus, + acctStake, configHook, phonebookHook, + // log Node 0 to stdout/testing log for debugging - in order to preserve the log after failure + &mixedLogFullNodeLoggerProvider{ + singleFileFullNodeLoggerProvider: singleFileFullNodeLoggerProvider{t: t}, + stdoutNodes: map[int]struct{}{0: {}}, + }) require.Len(t, nodes, 3) require.Len(t, wallets, 3) for i := 0; i < len(nodes); i++ { @@ -1049,7 +1102,7 @@ func TestNodeP2PRelays(t *testing.T) { return nil } - nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook) + nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook, &singleFileFullNodeLoggerProvider{t: t}) require.Len(t, nodes, 3) require.Len(t, wallets, 3) for i := 0; i < len(nodes); i++ { @@ -1203,7 +1256,7 @@ func TestNodeHybridP2PGossipSend(t *testing.T) { return nil } - nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook) + nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook, &singleFileFullNodeLoggerProvider{t: t}) require.Len(t, nodes, 3) require.Len(t, wallets, 3) for i := 0; i < len(nodes); i++ { @@ -1325,7 +1378,7 @@ func TestNodeP2P_NetProtoVersions(t *testing.T) { } return phonebook } - nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook) + nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook, &singleFileFullNodeLoggerProvider{t: t}) require.Len(t, nodes, numAccounts) require.Len(t, wallets, numAccounts) for i := 0; i < len(nodes); i++ { From f62cd3df966b917e9f7bacd9af744bf8fc902e68 Mon Sep 17 00:00:00 2001 From: cui <523516579@qq.com> Date: Mon, 18 Aug 2025 21:35:40 +0800 Subject: [PATCH 15/25] cmd/goal: refactor to use typeparam (#6413) --- cmd/goal/interact.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index cd6f2291f5..acbac3cccf 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -80,7 +80,7 @@ type appInteractDatum interface { pseudo() bool } -func helpList(help map[string]appInteractDatum) string { +func helpList[V appInteractDatum](help map[string]V) string { largestName := 0 largestKind := 0 for k, v := range help { @@ -286,11 +286,7 @@ func (sch appInteractSchema) validate() (err error) { } func (sch appInteractSchema) EntryList() string { - help := make(map[string]appInteractDatum) - for k, v := range sch { - help[k] = v - } - return helpList(help) + return helpList(sch) } func (sch appInteractSchema) EntryNames() (names []string) { @@ -434,11 +430,7 @@ func (hdr appInteractHeader) validate() (err error) { } func (hdr appInteractHeader) ProcList() string { - help := make(map[string]appInteractDatum) - for k, v := range hdr.Execute { - help[k] = v - } - return helpList(help) + return helpList(hdr.Execute) } func (hdr appInteractHeader) ProcNames() (names []string) { From 7563788fd3d2fd680ce2f573ae2636300fbc09bb Mon Sep 17 00:00:00 2001 From: nullun Date: Tue, 19 Aug 2025 15:29:55 +0100 Subject: [PATCH 16/25] Txn: move/add asset txn validation into their own wellFormed methods (#6396) Co-authored-by: John Jannotti --- data/transactions/asset.go | 48 ++++++++++++++++++++ data/transactions/logic/evalAppTxn_test.go | 52 +++++++++++++++++----- data/transactions/logic/ledger_test.go | 19 ++++---- data/transactions/transaction.go | 39 ++++++++++------ 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/data/transactions/asset.go b/data/transactions/asset.go index 93b6af840e..dc76d7bb8c 100644 --- a/data/transactions/asset.go +++ b/data/transactions/asset.go @@ -17,6 +17,10 @@ package transactions import ( + "errors" + "fmt" + + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" ) @@ -75,3 +79,47 @@ type AssetFreezeTxnFields struct { // AssetFrozen is the new frozen value. AssetFrozen bool `codec:"afrz"` } + +func (ac AssetConfigTxnFields) wellFormed(proto config.ConsensusParams) error { + if len(ac.AssetParams.AssetName) > proto.MaxAssetNameBytes { + return fmt.Errorf("transaction asset name too big: %d > %d", len(ac.AssetParams.AssetName), proto.MaxAssetNameBytes) + } + + if len(ac.AssetParams.UnitName) > proto.MaxAssetUnitNameBytes { + return fmt.Errorf("transaction asset unit name too big: %d > %d", len(ac.AssetParams.UnitName), proto.MaxAssetUnitNameBytes) + } + + if len(ac.AssetParams.URL) > proto.MaxAssetURLBytes { + return fmt.Errorf("transaction asset url too big: %d > %d", len(ac.AssetParams.URL), proto.MaxAssetURLBytes) + } + + if ac.AssetParams.Decimals > proto.MaxAssetDecimals { + return fmt.Errorf("transaction asset decimals is too high (max is %d)", proto.MaxAssetDecimals) + } + + return nil +} + +func (ax AssetTransferTxnFields) wellFormed() error { + if ax.XferAsset == 0 && ax.AssetAmount != 0 { + return errors.New("asset ID cannot be zero") + } + + if !ax.AssetSender.IsZero() && !ax.AssetCloseTo.IsZero() { + return errors.New("cannot close asset by clawback") + } + + return nil +} + +func (af AssetFreezeTxnFields) wellFormed() error { + if af.FreezeAsset == 0 { + return errors.New("asset ID cannot be zero") + } + + if af.FreezeAccount.IsZero() { + return errors.New("freeze account cannot be empty") + } + + return nil +} diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 3008d517d6..21dbc52087 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -74,9 +74,7 @@ func TestCurrentInnerTypes(t *testing.T) { TestApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") TestApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") - TestApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") TestApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") - TestApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") // allowed since v6 TestApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") @@ -431,6 +429,13 @@ func TestAppAxfer(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + closeWithClawback := ` + itxn_begin + int axfer ; itxn_field TypeEnum + txn Sender ; itxn_field AssetSender + txn Sender ; itxn_field AssetCloseTo + itxn_submit +` axfer := ` itxn_begin int 77 @@ -450,6 +455,8 @@ func TestAppAxfer(t *testing.T) { TestApp(t, source, ep, problem...) } + test(closeWithClawback, "cannot close asset by clawback") + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAsset(tx.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample ledger.NewAsset(tx.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample @@ -461,6 +468,14 @@ func TestAppAxfer(t *testing.T) { "assert failed") // app account not opted in ledger.NewAccount(appAddr(888), 10000) // plenty for fees + + // It should be possible to send 0 amount of an asset (existing + // or not) to any account but ourself. Regardless of being opted in + test("global CurrentApplicationAddress; txn Accounts 1; int 0" + axfer + "int 1") + holding, err := ledger.AssetHolding(appAddr(888), 77) + require.ErrorContains(t, err, "no asset 77 for account") + require.Equal(t, uint64(0), holding.Amount) + ledger.NewHolding(appAddr(888), 77, 3000, false) test("global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;") @@ -494,7 +509,7 @@ func TestAppAxfer(t *testing.T) { // is going to fail anyway, but to keep the behavior consistent, v9 // allows the zero asset (and zero account) in `requireHolding`. test("global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", - fmt.Sprintf("Sender (%s) not opted in to 0", appAddr(888))) + "asset ID cannot be zero") test("global CurrentApplicationAddress; txn Accounts 1; int 100" + axfer + "int 1") @@ -775,14 +790,7 @@ func TestAssetFreeze(t *testing.T) { int 5000 == ` - // v5 added inners - TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - // Give it enough for fees. Recall that we don't check min balance at this level. - ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee) - TestApp(t, create, ep) - - freeze := ` + freeze := ` itxn_begin int afrz ; itxn_field TypeEnum int 5000 ; itxn_field FreezeAsset @@ -791,6 +799,24 @@ func TestAssetFreeze(t *testing.T) { itxn_submit int 1 ` + missingFreezeAccount := ` + itxn_begin + int afrz ; itxn_field TypeEnum + int 5000 ; itxn_field FreezeAsset + itxn_submit +` + missingAssetID := ` + itxn_begin + int afrz ; itxn_field TypeEnum + itxn_submit +` + // v5 added inners + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + // Give it enough for fees. Recall that we don't check min balance at this level. + ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee) + TestApp(t, create, ep) + TestApp(t, freeze, ep, "unavailable Asset 5000") tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(5000)} tx.ApplicationArgs = [][]byte{{0x01}} @@ -805,6 +831,10 @@ func TestAssetFreeze(t *testing.T) { holding, err = ledger.AssetHolding(tx.Receiver, 5000) require.NoError(t, err) require.Equal(t, false, holding.Frozen) + + // Malformed + TestApp(t, missingFreezeAccount, ep, "freeze account cannot be empty") + TestApp(t, missingAssetID, ep, "asset ID cannot be zero") }) } diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index eb55406075..fb95d70d73 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -731,17 +731,20 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi } fholding, ok := fbr.holdings[aid] if !ok { - if from == to && amount == 0 { - // opt in - if params, exists := l.assets[aid]; exists { - fbr.holdings[aid] = basics.AssetHolding{ - Frozen: params.DefaultFrozen, + if amount == 0 { + if from == to { + // opt in + if params, exists := l.assets[aid]; exists { + fbr.holdings[aid] = basics.AssetHolding{ + Frozen: params.DefaultFrozen, + } + } else { + return fmt.Errorf("Asset (%d) does not exist", aid) } - return nil } - return fmt.Errorf("Asset (%d) does not exist", aid) + } else { + return fmt.Errorf("Sender (%s) not opted in to %d", from, aid) } - return fmt.Errorf("Sender (%s) not opted in to %d", from, aid) } if fholding.Frozen { return fmt.Errorf("Sender (%s) is frozen for %d", from, aid) diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 70b2068f3c..9ced317c04 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -337,11 +337,36 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa return err } - case protocol.AssetConfigTx, protocol.AssetTransferTx, protocol.AssetFreezeTx: + case protocol.AssetConfigTx: if !proto.Asset { return fmt.Errorf("asset transaction not supported") } + err := tx.AssetConfigTxnFields.wellFormed(proto) + if err != nil { + return err + } + + case protocol.AssetTransferTx: + if !proto.Asset { + return fmt.Errorf("asset transaction not supported") + } + + err := tx.AssetTransferTxnFields.wellFormed() + if err != nil { + return err + } + + case protocol.AssetFreezeTx: + if !proto.Asset { + return fmt.Errorf("asset transaction not supported") + } + + err := tx.AssetFreezeTxnFields.wellFormed() + if err != nil { + return err + } + case protocol.ApplicationCallTx: if !proto.Application { return fmt.Errorf("application transaction not supported") @@ -431,18 +456,6 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa if len(tx.Note) > proto.MaxTxnNoteBytes { return fmt.Errorf("transaction note too big: %d > %d", len(tx.Note), proto.MaxTxnNoteBytes) } - if len(tx.AssetConfigTxnFields.AssetParams.AssetName) > proto.MaxAssetNameBytes { - return fmt.Errorf("transaction asset name too big: %d > %d", len(tx.AssetConfigTxnFields.AssetParams.AssetName), proto.MaxAssetNameBytes) - } - if len(tx.AssetConfigTxnFields.AssetParams.UnitName) > proto.MaxAssetUnitNameBytes { - return fmt.Errorf("transaction asset unit name too big: %d > %d", len(tx.AssetConfigTxnFields.AssetParams.UnitName), proto.MaxAssetUnitNameBytes) - } - if len(tx.AssetConfigTxnFields.AssetParams.URL) > proto.MaxAssetURLBytes { - return fmt.Errorf("transaction asset url too big: %d > %d", len(tx.AssetConfigTxnFields.AssetParams.URL), proto.MaxAssetURLBytes) - } - if tx.AssetConfigTxnFields.AssetParams.Decimals > proto.MaxAssetDecimals { - return fmt.Errorf("transaction asset decimals is too high (max is %d)", proto.MaxAssetDecimals) - } if tx.Sender == spec.RewardsPool { // this check is just to be safe, but reaching here seems impossible, since it requires computing a preimage of rwpool return fmt.Errorf("transaction from incentive pool is invalid") From 19609553f6da52b6667a02ded1122f23714eacf6 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:52:28 -0400 Subject: [PATCH 17/25] CI: set -p 1 in nightly test parallelism (#6414) --- .github/workflows/ci-nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index c4ad34fc96..283475e416 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -157,7 +157,7 @@ jobs: PARTITION_ID: ${{ matrix.partition_id }} PARTITION_TOTAL: 2 E2E_TEST_FILTER: GO - PARALLEL_FLAG: "-p 4" + PARALLEL_FLAG: "-p 1" steps: - name: Download workspace archive uses: actions/download-artifact@v4 @@ -214,7 +214,7 @@ jobs: PARTITION_ID: ${{ matrix.partition_id }} PARTITION_TOTAL: 2 E2E_TEST_FILTER: EXPECT - PARALLEL_FLAG: "-p 4" + PARALLEL_FLAG: "-p 1" steps: - name: Download workspace archive uses: actions/download-artifact@v4 From fc6cac2678b2073653722961cbfdf15436afdcb1 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 21 Aug 2025 15:15:26 -0400 Subject: [PATCH 18/25] AVM: Prepare for falcon_verify in v12 (#6416) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- data/transactions/logic/assembler_test.go | 28 +++++++++++++++-------- data/transactions/logic/crypto_test.go | 2 +- data/transactions/logic/eval_test.go | 9 ++++++++ data/transactions/logic/opcodes.go | 8 +++---- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index cd44808753..49c545fffc 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -432,13 +432,17 @@ online_stake voter_params_get VoterIncentiveEligible ` -const stateProofNonsense = ` -pushbytes 0x0123456789abcd -sumhash512 +const fvNonsense = ` +pushbytes 0xabcd dup; dup falcon_verify ` +const sumhashNonsense = ` +pushbytes 0x0123 +sumhash512 +` + const mimcNonsense = ` pushbytes 0x11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff mimc BLS12_381Mp111 @@ -457,7 +461,9 @@ const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence const v11Nonsense = v10Nonsense + incentiveNonsense + mimcNonsense -const v12Nonsense = v11Nonsense + stateProofNonsense +const v12Nonsense = v11Nonsense + fvNonsense + +const v13Nonsense = v12Nonsense + sumhashNonsense const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" @@ -480,12 +486,14 @@ const spliceCompiled = "d2d3" const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled const incentiveCompiled = "757401" - -const stateProofCompiled = "80070123456789abcd86494985" const mimcCompiled = "802011223344556677889900aabbccddeeff11223344556677889900aabbccddeeffe601" - const v11Compiled = v10Compiled + incentiveCompiled + mimcCompiled -const v12Compiled = v11Compiled + stateProofCompiled + +const fvCompiled = "8002abcd494985" +const v12Compiled = v11Compiled + fvCompiled + +const sumhashCompiled = "8002012386" +const v13Compiled = v12Compiled + sumhashCompiled var nonsense = map[uint64]string{ 1: v1Nonsense, @@ -500,6 +508,7 @@ var nonsense = map[uint64]string{ 10: v10Nonsense, 11: v11Nonsense, 12: v12Nonsense, + 13: v13Nonsense, } var compiled = map[uint64]string{ @@ -515,6 +524,7 @@ var compiled = map[uint64]string{ 10: "0a" + v10Compiled, 11: "0b" + v11Compiled, 12: "0c" + v12Compiled, + 13: "0d" + v13Compiled, } func pseudoOp(opcode string) bool { @@ -568,7 +578,7 @@ func TestAssemble(t *testing.T) { } } -var experiments = []uint64{spOpcodesVersion} +var experiments = []uint64{sumhashVersion} // TestExperimental forces a conscious choice to promote "experimental" opcode // groups. This will fail when we increment vFuture's LogicSigVersion. If we had diff --git a/data/transactions/logic/crypto_test.go b/data/transactions/logic/crypto_test.go index d5a854cd4f..9caa1c19d5 100644 --- a/data/transactions/logic/crypto_test.go +++ b/data/transactions/logic/crypto_test.go @@ -76,7 +76,7 @@ func TestSumhash(t *testing.T) { } for _, v := range testVectors { - testAccepts(t, fmt.Sprintf(`byte "%s"; sumhash512; byte 0x%s; ==`, v.in, v.out), 12) + testAccepts(t, fmt.Sprintf(`byte "%s"; sumhash512; byte 0x%s; ==`, v.in, v.out), 13) } } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 40ec36bca2..3fc3028596 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1271,6 +1271,9 @@ global PayoutsMaxBalance; int 6; ==; assert const globalV12TestProgram = globalV11TestProgram + ` ` +const globalV13TestProgram = globalV12TestProgram + ` +` + func TestAllGlobals(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1294,6 +1297,7 @@ func TestAllGlobals(t *testing.T) { 10: {GenesisHash, globalV10TestProgram}, 11: {PayoutsMaxBalance, globalV11TestProgram}, 12: {PayoutsMaxBalance, globalV12TestProgram}, + 13: {PayoutsMaxBalance, globalV13TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) @@ -1811,6 +1815,10 @@ txn RejectVersion ! ` +const testTxnProgramTextV13 = testTxnProgramTextV12 + ` +assert +int 1` + func makeSampleTxn() transactions.SignedTxn { var txn transactions.SignedTxn copy(txn.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) @@ -1925,6 +1933,7 @@ func TestTxn(t *testing.T) { 10: testTxnProgramTextV10, 11: testTxnProgramTextV11, 12: testTxnProgramTextV12, + 13: testTxnProgramTextV13, } for i, txnField := range TxnFieldNames { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index e201171f7f..20acff0e72 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -28,7 +28,7 @@ import ( ) // LogicVersion defines default assembler and max eval versions -const LogicVersion = 12 +const LogicVersion = 13 // rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality // was enabled. This is important to remember so that old TEAL accounts cannot @@ -81,7 +81,7 @@ const mimcVersion = 11 // EXPERIMENTAL. These should be revisited whenever a new LogicSigVersion is // moved from vFuture to a new consensus version. If they remain unready, bump // their version, and fixup TestAssemble() in assembler_test.go. -const spOpcodesVersion = 12 // falcon_verify, sumhash512 +const sumhashVersion = 13 // Unlimited Global Storage opcodes const boxVersion = 8 // box_* @@ -655,8 +655,8 @@ var OpSpecs = []OpSpec{ {0x83, "pushints", opPushInts, proto(":", "", "[N items]").stackExplain(opPushIntsStackChange), 8, constants(asmPushInts, checkIntImmArgs, "uint ...", immInts).typed(typePushInts).trust()}, {0x84, "ed25519verify_bare", opEd25519VerifyBare, proto("bb{64}b{32}:T"), 7, costly(1900)}, - {0x85, "falcon_verify", opFalconVerify, proto("bb{1232}b{1793}:T"), spOpcodesVersion, costly(1700)}, // dynamic for internal hash? - {0x86, "sumhash512", opSumhash512, proto("b:b{64}"), spOpcodesVersion, costByLength(150, 7, 4, 0)}, + {0x85, "falcon_verify", opFalconVerify, proto("bb{1232}b{1793}:T"), 12, costly(1700)}, // dynamic for internal hash? + {0x86, "sumhash512", opSumhash512, proto("b:b{64}"), sumhashVersion, costByLength(150, 7, 4, 0)}, // "Function oriented" {0x88, "callsub", opCallSub, proto(":"), 4, detBranch()}, From 4bdaacdcf7804ca5acbb23987c1013cb2d472770 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 25 Aug 2025 13:18:03 -0400 Subject: [PATCH 19/25] Docs: v12 docs (#6418) --- cmd/opdoc/opdoc.go | 2 +- data/transactions/logic/README.md | 27 +- data/transactions/logic/README_in.md | 2 +- data/transactions/logic/TEAL_opcodes_v12.md | 1824 +++++++ data/transactions/logic/doc.go | 2 +- data/transactions/logic/langspec_v12.json | 4946 ++++++++++++++++++ data/transactions/logic/teal.tmLanguage.json | 2 +- 7 files changed, 6789 insertions(+), 16 deletions(-) create mode 100644 data/transactions/logic/TEAL_opcodes_v12.md create mode 100644 data/transactions/logic/langspec_v12.json diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index e2ce42028b..8e4cf3df84 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -443,7 +443,7 @@ func create(file string) *os.File { } func main() { - const docVersion = uint64(11) + const docVersion = uint64(12) opGroups := make(map[string][]string, len(logic.OpSpecs)) for grp, names := range logic.OpGroups { diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 930c8958af..41655ff09d 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -81,7 +81,7 @@ In order to maintain existing semantics for previously written programs, AVM code is versioned. When new opcodes are introduced, or behavior is changed, a new version is introduced. Programs carrying old versions are executed with their original semantics. In the AVM -bytecode, the version is an incrementing integer, currently 6, and +bytecode, the version is an incrementing integer, currently 12, and denoted vX throughout this document. ## Execution Modes @@ -500,6 +500,7 @@ these results may contain leading zero bytes. | `keccak256` | Keccak256 hash of value A, yields [32]byte | | `sha512_256` | SHA512_256 hash of value A, yields [32]byte | | `sha3_256` | SHA3_256 hash of value A, yields [32]byte | +| `falcon_verify` | for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey => {0 or 1} | | `ed25519verify` | for (data A, signature B, pubkey C) verify the signature of ("ProgData" \|\| program_hash \|\| data) against the pubkey => {0 or 1} | | `ed25519verify_bare` | for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} | | `ecdsa_verify v` | for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} | @@ -630,6 +631,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | 63 | StateProofPK | [64]byte | v6 | State proof public key | | 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages | | 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages | +| 68 | RejectVersion | uint64 | v12 | Application version for which the txn must reject | ##### Array Fields | Index | Name | Type | In | Notes | @@ -706,17 +708,18 @@ Asset fields include `AssetHolding` and `AssetParam` fields that are used in the App fields used in the `app_params_get` opcode. -| Index | Name | Type | Notes | -| - | ------ | -- | --------- | -| 0 | AppApprovalProgram | []byte | Bytecode of Approval Program | -| 1 | AppClearStateProgram | []byte | Bytecode of Clear State Program | -| 2 | AppGlobalNumUint | uint64 | Number of uint64 values allowed in Global State | -| 3 | AppGlobalNumByteSlice | uint64 | Number of byte array values allowed in Global State | -| 4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | -| 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | -| 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | -| 7 | AppCreator | address | Creator address | -| 8 | AppAddress | address | Address for which this application has authority | +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | AppApprovalProgram | []byte | | Bytecode of Approval Program | +| 1 | AppClearStateProgram | []byte | | Bytecode of Clear State Program | +| 2 | AppGlobalNumUint | uint64 | | Number of uint64 values allowed in Global State | +| 3 | AppGlobalNumByteSlice | uint64 | | Number of byte array values allowed in Global State | +| 4 | AppLocalNumUint | uint64 | | Number of uint64 values allowed in Local State | +| 5 | AppLocalNumByteSlice | uint64 | | Number of byte array values allowed in Local State | +| 6 | AppExtraProgramPages | uint64 | | Number of Extra Program Pages of code space | +| 7 | AppCreator | address | | Creator address | +| 8 | AppAddress | address | | Address for which this application has authority | +| 9 | AppVersion | uint64 | v12 | Version of the app, incremented each time the approval or clear program changes | **Account Fields** diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index fdf7a0df01..f6391c3c37 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -67,7 +67,7 @@ In order to maintain existing semantics for previously written programs, AVM code is versioned. When new opcodes are introduced, or behavior is changed, a new version is introduced. Programs carrying old versions are executed with their original semantics. In the AVM -bytecode, the version is an incrementing integer, currently 6, and +bytecode, the version is an incrementing integer, currently 12, and denoted vX throughout this document. ## Execution Modes diff --git a/data/transactions/logic/TEAL_opcodes_v12.md b/data/transactions/logic/TEAL_opcodes_v12.md new file mode 100644 index 0000000000..6586f11a5e --- /dev/null +++ b/data/transactions/logic/TEAL_opcodes_v12.md @@ -0,0 +1,1824 @@ +# v12 Opcodes + +Ops have a 'cost' of 1 unless otherwise specified. + + +## err + +- Bytecode: 0x00 +- Stack: ... → _exits_ +- Fail immediately. + +## sha256 + +- Bytecode: 0x01 +- Stack: ..., A: []byte → ..., [32]byte +- SHA256 hash of value A, yields [32]byte +- **Cost**: 35 + +## keccak256 + +- Bytecode: 0x02 +- Stack: ..., A: []byte → ..., [32]byte +- Keccak256 hash of value A, yields [32]byte +- **Cost**: 130 + +## sha512_256 + +- Bytecode: 0x03 +- Stack: ..., A: []byte → ..., [32]byte +- SHA512_256 hash of value A, yields [32]byte +- **Cost**: 45 + +## ed25519verify + +- Bytecode: 0x04 +- Stack: ..., A: []byte, B: [64]byte, C: [32]byte → ..., bool +- for (data A, signature B, pubkey C) verify the signature of ("ProgData" || program_hash || data) against the pubkey => {0 or 1} +- **Cost**: 1900 + +The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack. + +## ecdsa_verify + +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) +- Bytecode: 0x05 {uint8} +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool +- for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} +- **Cost**: Secp256k1=1700; Secp256r1=2500 +- Availability: v5 + +### ECDSA + +Curves + +| Index | Name | In | Notes | +| - | ------ | - | --------- | +| 0 | Secp256k1 | | secp256k1 curve, used in Bitcoin | +| 1 | Secp256r1 | v7 | secp256r1 curve, NIST standard | + + +The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted. + +## ecdsa_pk_decompress + +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) +- Bytecode: 0x06 {uint8} +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte +- decompress pubkey A into components X, Y +- **Cost**: Secp256k1=650; Secp256r1=2400 +- Availability: v5 + +The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded. + +## ecdsa_pk_recover + +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) +- Bytecode: 0x07 {uint8} +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte +- for (data A, recovery id B, signature C, D) recover a public key +- **Cost**: 2000 +- Availability: v5 + +S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long. + +## + + +- Bytecode: 0x08 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A plus B. Fail on overflow. + +Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `addw`. + +## - + +- Bytecode: 0x09 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A minus B. Fail if B > A. + +## / + +- Bytecode: 0x0a +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A divided by B (truncated division). Fail if B == 0. + +`divmodw` is available to divide the two-element values produced by `mulw` and `addw`. + +## * + +- Bytecode: 0x0b +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A times B. Fail on overflow. + +Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `mulw`. + +## < + +- Bytecode: 0x0c +- Stack: ..., A: uint64, B: uint64 → ..., bool +- A less than B => {0 or 1} + +## > + +- Bytecode: 0x0d +- Stack: ..., A: uint64, B: uint64 → ..., bool +- A greater than B => {0 or 1} + +## <= + +- Bytecode: 0x0e +- Stack: ..., A: uint64, B: uint64 → ..., bool +- A less than or equal to B => {0 or 1} + +## >= + +- Bytecode: 0x0f +- Stack: ..., A: uint64, B: uint64 → ..., bool +- A greater than or equal to B => {0 or 1} + +## && + +- Bytecode: 0x10 +- Stack: ..., A: uint64, B: uint64 → ..., bool +- A is not zero and B is not zero => {0 or 1} + +## || + +- Bytecode: 0x11 +- Stack: ..., A: uint64, B: uint64 → ..., bool +- A is not zero or B is not zero => {0 or 1} + +## == + +- Bytecode: 0x12 +- Stack: ..., A, B → ..., bool +- A is equal to B => {0 or 1} + +## != + +- Bytecode: 0x13 +- Stack: ..., A, B → ..., bool +- A is not equal to B => {0 or 1} + +## ! + +- Bytecode: 0x14 +- Stack: ..., A: uint64 → ..., uint64 +- A == 0 yields 1; else 0 + +## len + +- Bytecode: 0x15 +- Stack: ..., A: []byte → ..., uint64 +- yields length of byte value A + +## itob + +- Bytecode: 0x16 +- Stack: ..., A: uint64 → ..., [8]byte +- converts uint64 A to big-endian byte array, always of length 8 + +## btoi + +- Bytecode: 0x17 +- Stack: ..., A: []byte → ..., uint64 +- converts big-endian byte array A to uint64. Fails if len(A) > 8. Padded by leading 0s if len(A) < 8. + +`btoi` fails if the input is longer than 8 bytes. + +## % + +- Bytecode: 0x18 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A modulo B. Fail if B == 0. + +## | + +- Bytecode: 0x19 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A bitwise-or B + +## & + +- Bytecode: 0x1a +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A bitwise-and B + +## ^ + +- Bytecode: 0x1b +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A bitwise-xor B + +## ~ + +- Bytecode: 0x1c +- Stack: ..., A: uint64 → ..., uint64 +- bitwise invert value A + +## mulw + +- Bytecode: 0x1d +- Stack: ..., A: uint64, B: uint64 → ..., X: uint64, Y: uint64 +- A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low + +## addw + +- Bytecode: 0x1e +- Stack: ..., A: uint64, B: uint64 → ..., X: uint64, Y: uint64 +- A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits. +- Availability: v2 + +## divmodw + +- Bytecode: 0x1f +- Stack: ..., A: uint64, B: uint64, C: uint64, D: uint64 → ..., W: uint64, X: uint64, Y: uint64, Z: uint64 +- W,X = (A,B / C,D); Y,Z = (A,B modulo C,D) +- **Cost**: 20 +- Availability: v4 + +The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low. + +## intcblock + +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values +- Bytecode: 0x20 {varuint count, [varuint ...]} +- Stack: ... → ... +- prepare block of uint64 constants for use by intc + +`intcblock` loads following program bytes into an array of integer constants in the evaluator. These integer constants can be referred to by `intc` and `intc_*` which will push the value onto the stack. Subsequent calls to `intcblock` reset and replace the integer constants available to the script. + +## intc + +- Syntax: `intc I` where I: an index in the intcblock +- Bytecode: 0x21 {uint8} +- Stack: ... → ..., uint64 +- Ith constant from intcblock + +## intc_0 + +- Bytecode: 0x22 +- Stack: ... → ..., uint64 +- constant 0 from intcblock + +## intc_1 + +- Bytecode: 0x23 +- Stack: ... → ..., uint64 +- constant 1 from intcblock + +## intc_2 + +- Bytecode: 0x24 +- Stack: ... → ..., uint64 +- constant 2 from intcblock + +## intc_3 + +- Bytecode: 0x25 +- Stack: ... → ..., uint64 +- constant 3 from intcblock + +## bytecblock + +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values +- Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} +- Stack: ... → ... +- prepare block of byte-array constants for use by bytec + +`bytecblock` loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by `bytec` and `bytec_*` which will push the value onto the stack. Subsequent calls to `bytecblock` reset and replace the bytes constants available to the script. + +## bytec + +- Syntax: `bytec I` where I: an index in the bytecblock +- Bytecode: 0x27 {uint8} +- Stack: ... → ..., []byte +- Ith constant from bytecblock + +## bytec_0 + +- Bytecode: 0x28 +- Stack: ... → ..., []byte +- constant 0 from bytecblock + +## bytec_1 + +- Bytecode: 0x29 +- Stack: ... → ..., []byte +- constant 1 from bytecblock + +## bytec_2 + +- Bytecode: 0x2a +- Stack: ... → ..., []byte +- constant 2 from bytecblock + +## bytec_3 + +- Bytecode: 0x2b +- Stack: ... → ..., []byte +- constant 3 from bytecblock + +## arg + +- Syntax: `arg N` where N: an arg index +- Bytecode: 0x2c {uint8} +- Stack: ... → ..., []byte +- Nth LogicSig argument +- Mode: Signature + +## arg_0 + +- Bytecode: 0x2d +- Stack: ... → ..., []byte +- LogicSig argument 0 +- Mode: Signature + +## arg_1 + +- Bytecode: 0x2e +- Stack: ... → ..., []byte +- LogicSig argument 1 +- Mode: Signature + +## arg_2 + +- Bytecode: 0x2f +- Stack: ... → ..., []byte +- LogicSig argument 2 +- Mode: Signature + +## arg_3 + +- Bytecode: 0x30 +- Stack: ... → ..., []byte +- LogicSig argument 3 +- Mode: Signature + +## txn + +- Syntax: `txn F` where F: [txn](#field-group-txn) +- Bytecode: 0x31 {uint8} +- Stack: ... → ..., any +- field F of current transaction + +### txn + +Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)) + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | Sender | address | | 32 byte address | +| 1 | Fee | uint64 | | microalgos | +| 2 | FirstValid | uint64 | | round number | +| 3 | FirstValidTime | uint64 | v7 | UNIX timestamp of block before txn.FirstValid. Fails if negative | +| 4 | LastValid | uint64 | | round number | +| 5 | Note | []byte | | Any data up to 1024 bytes | +| 6 | Lease | [32]byte | | 32 byte lease value | +| 7 | Receiver | address | | 32 byte address | +| 8 | Amount | uint64 | | microalgos | +| 9 | CloseRemainderTo | address | | 32 byte address | +| 10 | VotePK | [32]byte | | 32 byte address | +| 11 | SelectionPK | [32]byte | | 32 byte address | +| 12 | VoteFirst | uint64 | | The first round that the participation key is valid. | +| 13 | VoteLast | uint64 | | The last round that the participation key is valid. | +| 14 | VoteKeyDilution | uint64 | | Dilution for the 2-level participation key | +| 15 | Type | []byte | | Transaction type as bytes | +| 16 | TypeEnum | uint64 | | Transaction type as integer | +| 17 | XferAsset | uint64 | | Asset ID | +| 18 | AssetAmount | uint64 | | value in Asset's units | +| 19 | AssetSender | address | | 32 byte address. Source of assets if Sender is the Asset's Clawback address. | +| 20 | AssetReceiver | address | | 32 byte address | +| 21 | AssetCloseTo | address | | 32 byte address | +| 22 | GroupIndex | uint64 | | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | +| 23 | TxID | [32]byte | | The computed ID for this transaction. 32 bytes. | +| 24 | ApplicationID | uint64 | v2 | ApplicationID from ApplicationCall transaction | +| 25 | OnCompletion | uint64 | v2 | ApplicationCall transaction on completion action | +| 27 | NumAppArgs | uint64 | v2 | Number of ApplicationArgs | +| 29 | NumAccounts | uint64 | v2 | Number of Accounts | +| 30 | ApprovalProgram | []byte | v2 | Approval program | +| 31 | ClearStateProgram | []byte | v2 | Clear state program | +| 32 | RekeyTo | address | v2 | 32 byte Sender's new AuthAddr | +| 33 | ConfigAsset | uint64 | v2 | Asset ID in asset config transaction | +| 34 | ConfigAssetTotal | uint64 | v2 | Total number of units of this asset created | +| 35 | ConfigAssetDecimals | uint64 | v2 | Number of digits to display after the decimal place when displaying the asset | +| 36 | ConfigAssetDefaultFrozen | bool | v2 | Whether the asset's slots are frozen by default or not, 0 or 1 | +| 37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset | +| 38 | ConfigAssetName | []byte | v2 | The asset name | +| 39 | ConfigAssetURL | []byte | v2 | URL | +| 40 | ConfigAssetMetadataHash | [32]byte | v2 | 32 byte commitment to unspecified asset metadata | +| 41 | ConfigAssetManager | address | v2 | 32 byte address | +| 42 | ConfigAssetReserve | address | v2 | 32 byte address | +| 43 | ConfigAssetFreeze | address | v2 | 32 byte address | +| 44 | ConfigAssetClawback | address | v2 | 32 byte address | +| 45 | FreezeAsset | uint64 | v2 | Asset ID being frozen or un-frozen | +| 46 | FreezeAssetAccount | address | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen | +| 47 | FreezeAssetFrozen | bool | v2 | The new frozen value, 0 or 1 | +| 49 | NumAssets | uint64 | v3 | Number of Assets | +| 51 | NumApplications | uint64 | v3 | Number of Applications | +| 52 | GlobalNumUint | uint64 | v3 | Number of global state integers in ApplicationCall | +| 53 | GlobalNumByteSlice | uint64 | v3 | Number of global state byteslices in ApplicationCall | +| 54 | LocalNumUint | uint64 | v3 | Number of local state integers in ApplicationCall | +| 55 | LocalNumByteSlice | uint64 | v3 | Number of local state byteslices in ApplicationCall | +| 56 | ExtraProgramPages | uint64 | v4 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. | +| 57 | Nonparticipation | bool | v5 | Marks an account nonparticipating for rewards | +| 59 | NumLogs | uint64 | v5 | Number of Logs (only with `itxn` in v5). Application mode only | +| 60 | CreatedAssetID | uint64 | v5 | Asset ID allocated by the creation of an ASA (only with `itxn` in v5). Application mode only | +| 61 | CreatedApplicationID | uint64 | v5 | ApplicationID allocated by the creation of an application (only with `itxn` in v5). Application mode only | +| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only | +| 63 | StateProofPK | [64]byte | v6 | State proof public key | +| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages | +| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages | +| 68 | RejectVersion | uint64 | v12 | Application version for which the txn must reject | + + +## global + +- Syntax: `global F` where F: [global](#field-group-global) +- Bytecode: 0x32 {uint8} +- Stack: ... → ..., any +- global field F + +### global + +Fields + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | MinTxnFee | uint64 | | microalgos | +| 1 | MinBalance | uint64 | | microalgos | +| 2 | MaxTxnLife | uint64 | | rounds | +| 3 | ZeroAddress | address | | 32 byte address of all zero bytes | +| 4 | GroupSize | uint64 | | Number of transactions in this atomic transaction group. At least 1 | +| 5 | LogicSigVersion | uint64 | v2 | Maximum supported version | +| 6 | Round | uint64 | v2 | Current round number. Application mode only. | +| 7 | LatestTimestamp | uint64 | v2 | Last confirmed block UNIX timestamp. Fails if negative. Application mode only. | +| 8 | CurrentApplicationID | uint64 | v2 | ID of current application executing. Application mode only. | +| 9 | CreatorAddress | address | v3 | Address of the creator of the current application. Application mode only. | +| 10 | CurrentApplicationAddress | address | v5 | Address that the current application controls. Application mode only. | +| 11 | GroupID | [32]byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. | +| 12 | OpcodeBudget | uint64 | v6 | The remaining cost that can be spent by opcodes in this program. | +| 13 | CallerApplicationID | uint64 | v6 | The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only. | +| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | +| 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. | +| 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. | +| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. | +| 18 | PayoutsEnabled | bool | v11 | Whether block proposal payouts are enabled. | +| 19 | PayoutsGoOnlineFee | uint64 | v11 | The fee required in a keyreg transaction to make an account incentive eligible. | +| 20 | PayoutsPercent | uint64 | v11 | The percentage of transaction fees in a block that can be paid to the block proposer. | +| 21 | PayoutsMinBalance | uint64 | v11 | The minimum balance an account must have in the agreement round to receive block payouts in the proposal round. | +| 22 | PayoutsMaxBalance | uint64 | v11 | The maximum balance an account can have in the agreement round to receive block payouts in the proposal round. | + + +## gtxn + +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) +- Bytecode: 0x33 {uint8}, {uint8} +- Stack: ... → ..., any +- field F of the Tth transaction in the current group + +for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`. + +## load + +- Syntax: `load I` where I: position in scratch space to load from +- Bytecode: 0x34 {uint8} +- Stack: ... → ..., any +- Ith scratch space value. All scratch spaces are 0 at program start. + +## store + +- Syntax: `store I` where I: position in scratch space to store to +- Bytecode: 0x35 {uint8} +- Stack: ..., A → ... +- store A to the Ith scratch space + +## txna + +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0x36 {uint8}, {uint8} +- Stack: ... → ..., any +- Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. +- Availability: v2 + +### txna + +Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)) + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 26 | ApplicationArgs | []byte | v2 | Arguments passed to the application in the ApplicationCall transaction | +| 28 | Accounts | address | v2 | Accounts listed in the ApplicationCall transaction | +| 48 | Assets | uint64 | v3 | Foreign Assets listed in the ApplicationCall transaction | +| 50 | Applications | uint64 | v3 | Foreign Apps listed in the ApplicationCall transaction | +| 58 | Logs | []byte | v5 | Log messages emitted by an application call (only with `itxn` in v5). Application mode only | +| 64 | ApprovalProgramPages | []byte | v7 | Approval Program as an array of pages | +| 66 | ClearStateProgramPages | []byte | v7 | ClearState Program as an array of pages | + + +## gtxna + +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0x37 {uint8}, {uint8}, {uint8} +- Stack: ... → ..., any +- Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. +- Availability: v2 + +## gtxns + +- Syntax: `gtxns F` where F: [txn](#field-group-txn) +- Bytecode: 0x38 {uint8} +- Stack: ..., A: uint64 → ..., any +- field F of the Ath transaction in the current group +- Availability: v3 + +for notes on transaction fields available, see `txn`. If top of stack is _i_, `gtxns field` is equivalent to `gtxn _i_ field`. gtxns exists so that _i_ can be calculated, often based on the index of the current transaction. + +## gtxnsa + +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0x39 {uint8}, {uint8} +- Stack: ..., A: uint64 → ..., any +- Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. +- Availability: v3 + +## gload + +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from +- Bytecode: 0x3a {uint8}, {uint8} +- Stack: ... → ..., any +- Ith scratch space value of the Tth transaction in the current group +- Availability: v4 +- Mode: Application + +`gload` fails unless the requested transaction is an ApplicationCall and T < GroupIndex. + +## gloads + +- Syntax: `gloads I` where I: position in scratch space to load from +- Bytecode: 0x3b {uint8} +- Stack: ..., A: uint64 → ..., any +- Ith scratch space value of the Ath transaction in the current group +- Availability: v4 +- Mode: Application + +`gloads` fails unless the requested transaction is an ApplicationCall and A < GroupIndex. + +## gaid + +- Syntax: `gaid T` where T: transaction group index +- Bytecode: 0x3c {uint8} +- Stack: ... → ..., uint64 +- ID of the asset or application created in the Tth transaction of the current group +- Availability: v4 +- Mode: Application + +`gaid` fails unless the requested transaction created an asset or application and T < GroupIndex. + +## gaids + +- Bytecode: 0x3d +- Stack: ..., A: uint64 → ..., uint64 +- ID of the asset or application created in the Ath transaction of the current group +- Availability: v4 +- Mode: Application + +`gaids` fails unless the requested transaction created an asset or application and A < GroupIndex. + +## loads + +- Bytecode: 0x3e +- Stack: ..., A: uint64 → ..., any +- Ath scratch space value. All scratch spaces are 0 at program start. +- Availability: v5 + +## stores + +- Bytecode: 0x3f +- Stack: ..., A: uint64, B → ... +- store B to the Ath scratch space +- Availability: v5 + +## bnz + +- Syntax: `bnz TARGET` where TARGET: branch offset +- Bytecode: 0x40 {int16 (big-endian)} +- Stack: ..., A: uint64 → ... +- branch to TARGET if value A is not zero + +The `bnz` instruction opcode 0x40 is followed by two immediate data bytes which are a high byte first and low byte second which together form a 16 bit offset which the instruction may branch to. For a bnz instruction at `pc`, if the last element of the stack is not zero then branch to instruction at `pc + 3 + N`, else proceed to next instruction at `pc + 3`. Branch targets must be aligned instructions. (e.g. Branching to the second byte of a 2 byte op will be rejected.) Starting at v4, the offset is treated as a signed 16 bit integer allowing for backward branches and looping. In prior version (v1 to v3), branch offsets are limited to forward branches only, 0-0x7fff. + +At v2 it became allowed to branch to the end of the program exactly after the last instruction: bnz to byte N (with 0-indexing) was illegal for a TEAL program with N bytes before v2, and is legal after it. This change eliminates the need for a last instruction of no-op as a branch target at the end. (Branching beyond the end--in other words, to a byte larger than N--is still illegal and will cause the program to fail.) + +## bz + +- Syntax: `bz TARGET` where TARGET: branch offset +- Bytecode: 0x41 {int16 (big-endian)} +- Stack: ..., A: uint64 → ... +- branch to TARGET if value A is zero +- Availability: v2 + +See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. + +## b + +- Syntax: `b TARGET` where TARGET: branch offset +- Bytecode: 0x42 {int16 (big-endian)} +- Stack: ... → ... +- branch unconditionally to TARGET +- Availability: v2 + +See `bnz` for details on how branches work. `b` always jumps to the offset. + +## return + +- Bytecode: 0x43 +- Stack: ..., A: uint64 → _exits_ +- use A as success value; end +- Availability: v2 + +## assert + +- Bytecode: 0x44 +- Stack: ..., A: uint64 → ... +- immediately fail unless A is a non-zero number +- Availability: v3 + +## bury + +- Syntax: `bury N` where N: depth +- Bytecode: 0x45 {uint8} +- Stack: ..., A → ... +- replace the Nth value from the top of the stack with A. bury 0 fails. +- Availability: v8 + +## popn + +- Syntax: `popn N` where N: stack depth +- Bytecode: 0x46 {uint8} +- Stack: ..., [N items] → ... +- remove N values from the top of the stack +- Availability: v8 + +## dupn + +- Syntax: `dupn N` where N: copy count +- Bytecode: 0x47 {uint8} +- Stack: ..., A → ..., A, [N copies of A] +- duplicate A, N times +- Availability: v8 + +## pop + +- Bytecode: 0x48 +- Stack: ..., A → ... +- discard A + +## dup + +- Bytecode: 0x49 +- Stack: ..., A → ..., A, A +- duplicate A + +## dup2 + +- Bytecode: 0x4a +- Stack: ..., A, B → ..., A, B, A, B +- duplicate A and B +- Availability: v2 + +## dig + +- Syntax: `dig N` where N: depth +- Bytecode: 0x4b {uint8} +- Stack: ..., A, [N items] → ..., A, [N items], A +- Nth value from the top of the stack. dig 0 is equivalent to dup +- Availability: v3 + +## swap + +- Bytecode: 0x4c +- Stack: ..., A, B → ..., B, A +- swaps A and B on stack +- Availability: v3 + +## select + +- Bytecode: 0x4d +- Stack: ..., A, B, C: uint64 → ..., A or B +- selects one of two values based on top-of-stack: B if C != 0, else A +- Availability: v3 + +## cover + +- Syntax: `cover N` where N: depth +- Bytecode: 0x4e {uint8} +- Stack: ..., [N items], A → ..., A, [N items] +- remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. +- Availability: v5 + +## uncover + +- Syntax: `uncover N` where N: depth +- Bytecode: 0x4f {uint8} +- Stack: ..., A, [N items] → ..., [N items], A +- remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. +- Availability: v5 + +## concat + +- Bytecode: 0x50 +- Stack: ..., A: []byte, B: []byte → ..., []byte +- join A and B +- Availability: v2 + +`concat` fails if the result would be greater than 4096 bytes. + +## substring + +- Syntax: `substring S E` where S: start position, E: end position +- Bytecode: 0x51 {uint8}, {uint8} +- Stack: ..., A: []byte → ..., []byte +- A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails +- Availability: v2 + +## substring3 + +- Bytecode: 0x52 +- Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte +- A range of bytes from A starting at B up to but not including C. If C < B, or either is larger than the array length, the program fails +- Availability: v2 + +## getbit + +- Bytecode: 0x53 +- Stack: ..., A, B: uint64 → ..., uint64 +- Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails +- Availability: v3 + +see explanation of bit ordering in setbit + +## setbit + +- Bytecode: 0x54 +- Stack: ..., A, B: uint64, C: uint64 → ..., any +- Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails +- Availability: v3 + +When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10. + +## getbyte + +- Bytecode: 0x55 +- Stack: ..., A: []byte, B: uint64 → ..., uint64 +- Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails +- Availability: v3 + +## setbyte + +- Bytecode: 0x56 +- Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte +- Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails +- Availability: v3 + +## extract + +- Syntax: `extract S L` where S: start position, L: length +- Bytecode: 0x57 {uint8}, {uint8} +- Stack: ..., A: []byte → ..., []byte +- A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails +- Availability: v5 + +## extract3 + +- Bytecode: 0x58 +- Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte +- A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails
`extract3` can be called using `extract` with no immediates. +- Availability: v5 + +## extract_uint16 + +- Bytecode: 0x59 +- Stack: ..., A: []byte, B: uint64 → ..., uint64 +- A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails +- Availability: v5 + +## extract_uint32 + +- Bytecode: 0x5a +- Stack: ..., A: []byte, B: uint64 → ..., uint64 +- A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails +- Availability: v5 + +## extract_uint64 + +- Bytecode: 0x5b +- Stack: ..., A: []byte, B: uint64 → ..., uint64 +- A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails +- Availability: v5 + +## replace2 + +- Syntax: `replace2 S` where S: start position +- Bytecode: 0x5c {uint8} +- Stack: ..., A: []byte, B: []byte → ..., []byte +- Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
`replace2` can be called using `replace` with 1 immediate. +- Availability: v7 + +## replace3 + +- Bytecode: 0x5d +- Stack: ..., A: []byte, B: uint64, C: []byte → ..., []byte +- Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)
`replace3` can be called using `replace` with no immediates. +- Availability: v7 + +## base64_decode + +- Syntax: `base64_decode E` where E: [base64](#field-group-base64) +- Bytecode: 0x5e {uint8} +- Stack: ..., A: []byte → ..., []byte +- decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E +- **Cost**: 1 + 1 per 16 bytes of A +- Availability: v7 + +### base64 + +Encodings + +| Index | Name | Notes | +| - | ------ | --------- | +| 0 | URLEncoding | | +| 1 | StdEncoding | | + + +*Warning*: Usage should be restricted to very rare use cases. In almost all cases, smart contracts should directly handle non-encoded byte-strings. This opcode should only be used in cases where base64 is the only available option, e.g. interoperability with a third-party that only signs base64 strings. + + Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See [RFC 4648 sections 4 and 5](https://rfc-editor.org/rfc/rfc4648.html#section-4). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\n` and `\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\r`, or `\n`. + +## json_ref + +- Syntax: `json_ref R` where R: [json_ref](#field-group-json_ref) +- Bytecode: 0x5f {uint8} +- Stack: ..., A: []byte, B: []byte → ..., any +- key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A +- **Cost**: 25 + 2 per 7 bytes of A +- Availability: v7 + +### json_ref + +Types + +| Index | Name | Type | Notes | +| - | ------ | -- | --------- | +| 0 | JSONString | []byte | | +| 1 | JSONUint64 | uint64 | | +| 2 | JSONObject | []byte | | + + +*Warning*: Usage should be restricted to very rare use cases, as JSON decoding is expensive and quite limited. In addition, JSON objects are large and not optimized for size. + +Almost all smart contracts should use simpler and smaller methods (such as the [ABI](https://arc.algorand.foundation/ARCs/arc-0004). This opcode should only be used in cases where JSON is only available option, e.g. when a third-party only signs JSON. + +## balance + +- Bytecode: 0x60 +- Stack: ..., A → ..., uint64 +- balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following `itxn_submit` +- Availability: v2 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value. + +## app_opted_in + +- Bytecode: 0x61 +- Stack: ..., A, B: uint64 → ..., bool +- 1 if account A is opted in to application B, else 0 +- Availability: v2 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise. + +## app_local_get + +- Bytecode: 0x62 +- Stack: ..., A, B: stateKey → ..., any +- local state of the key B in the current application in account A +- Availability: v2 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), state key. Return: value. The value is zero (of type uint64) if the key does not exist. + +## app_local_get_ex + +- Bytecode: 0x63 +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool +- X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 +- Availability: v2 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist. + +## app_global_get + +- Bytecode: 0x64 +- Stack: ..., A: stateKey → ..., any +- global state of the key A in the current application +- Availability: v2 +- Mode: Application + +params: state key. Return: value. The value is zero (of type uint64) if the key does not exist. + +## app_global_get_ex + +- Bytecode: 0x65 +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool +- X is the global state of application A, key B. Y is 1 if key existed, else 0 +- Availability: v2 +- Mode: Application + +params: Txn.ForeignApps offset (or, since v4, an _available_ application id), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist. + +## app_local_put + +- Bytecode: 0x66 +- Stack: ..., A, B: stateKey, C → ... +- write C to key B in account A's local state of the current application +- Availability: v2 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), state key, value. + +## app_global_put + +- Bytecode: 0x67 +- Stack: ..., A: stateKey, B → ... +- write B to key A in the global state of the current application +- Availability: v2 +- Mode: Application + +## app_local_del + +- Bytecode: 0x68 +- Stack: ..., A, B: stateKey → ... +- delete key B from account A's local state of the current application +- Availability: v2 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), state key. + +Deleting a key which is already absent has no effect on the application local state. (In particular, it does _not_ cause the program to fail.) + +## app_global_del + +- Bytecode: 0x69 +- Stack: ..., A: stateKey → ... +- delete key A from the global state of the current application +- Availability: v2 +- Mode: Application + +params: state key. + +Deleting a key which is already absent has no effect on the application global state. (In particular, it does _not_ cause the program to fail.) + +## asset_holding_get + +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) +- Bytecode: 0x70 {uint8} +- Stack: ..., A, B: uint64 → ..., X: any, Y: bool +- X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 +- Availability: v2 +- Mode: Application + +### asset_holding + +Fields + +| Index | Name | Type | Notes | +| - | ------ | -- | --------- | +| 0 | AssetBalance | uint64 | Amount of the asset unit held by this account | +| 1 | AssetFrozen | bool | Is the asset frozen or not | + + +params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value. + +## asset_params_get + +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) +- Bytecode: 0x71 {uint8} +- Stack: ..., A: uint64 → ..., X: any, Y: bool +- X is field F from asset A. Y is 1 if A exists, else 0 +- Availability: v2 +- Mode: Application + +### asset_params + +Fields + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | AssetTotal | uint64 | | Total number of units of this asset | +| 1 | AssetDecimals | uint64 | | See AssetParams.Decimals | +| 2 | AssetDefaultFrozen | bool | | Frozen by default or not | +| 3 | AssetUnitName | []byte | | Asset unit name | +| 4 | AssetName | []byte | | Asset name | +| 5 | AssetURL | []byte | | URL with additional info about the asset | +| 6 | AssetMetadataHash | [32]byte | | Arbitrary commitment | +| 7 | AssetManager | address | | Manager address | +| 8 | AssetReserve | address | | Reserve address | +| 9 | AssetFreeze | address | | Freeze address | +| 10 | AssetClawback | address | | Clawback address | +| 11 | AssetCreator | address | v5 | Creator address | + + +params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value. + +## app_params_get + +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) +- Bytecode: 0x72 {uint8} +- Stack: ..., A: uint64 → ..., X: any, Y: bool +- X is field F from app A. Y is 1 if A exists, else 0 +- Availability: v5 +- Mode: Application + +### app_params + +Fields + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | AppApprovalProgram | []byte | | Bytecode of Approval Program | +| 1 | AppClearStateProgram | []byte | | Bytecode of Clear State Program | +| 2 | AppGlobalNumUint | uint64 | | Number of uint64 values allowed in Global State | +| 3 | AppGlobalNumByteSlice | uint64 | | Number of byte array values allowed in Global State | +| 4 | AppLocalNumUint | uint64 | | Number of uint64 values allowed in Local State | +| 5 | AppLocalNumByteSlice | uint64 | | Number of byte array values allowed in Local State | +| 6 | AppExtraProgramPages | uint64 | | Number of Extra Program Pages of code space | +| 7 | AppCreator | address | | Creator address | +| 8 | AppAddress | address | | Address for which this application has authority | +| 9 | AppVersion | uint64 | v12 | Version of the app, incremented each time the approval or clear program changes | + + +params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value. + +## acct_params_get + +- Syntax: `acct_params_get F` where F: [acct_params](#field-group-acct_params) +- Bytecode: 0x73 {uint8} +- Stack: ..., A → ..., X: any, Y: bool +- X is field F from account A. Y is 1 if A owns positive algos, else 0 +- Availability: v6 +- Mode: Application + +### acct_params + +Fields + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | AcctBalance | uint64 | | Account balance in microalgos | +| 1 | AcctMinBalance | uint64 | | Minimum required balance for account, in microalgos | +| 2 | AcctAuthAddr | address | | Address the account is rekeyed to. | +| 3 | AcctTotalNumUint | uint64 | v8 | The total number of uint64 values allocated by this account in Global and Local States. | +| 4 | AcctTotalNumByteSlice | uint64 | v8 | The total number of byte array values allocated by this account in Global and Local States. | +| 5 | AcctTotalExtraAppPages | uint64 | v8 | The number of extra app code pages used by this account. | +| 6 | AcctTotalAppsCreated | uint64 | v8 | The number of existing apps created by this account. | +| 7 | AcctTotalAppsOptedIn | uint64 | v8 | The number of apps this account is opted into. | +| 8 | AcctTotalAssetsCreated | uint64 | v8 | The number of existing ASAs created by this account. | +| 9 | AcctTotalAssets | uint64 | v8 | The numbers of ASAs held by this account (including ASAs this account created). | +| 10 | AcctTotalBoxes | uint64 | v8 | The number of existing boxes created by this account's app. | +| 11 | AcctTotalBoxBytes | uint64 | v8 | The total number of bytes used by this account's app's box keys and values. | +| 12 | AcctIncentiveEligible | bool | v11 | Has this account opted into block payouts | +| 13 | AcctLastProposed | uint64 | v11 | The round number of the last block this account proposed. | +| 14 | AcctLastHeartbeat | uint64 | v11 | The round number of the last block this account sent a heartbeat. | + + +## voter_params_get + +- Syntax: `voter_params_get F` where F: [voter_params](#field-group-voter_params) +- Bytecode: 0x74 {uint8} +- Stack: ..., A → ..., X: any, Y: bool +- X is field F from online account A as of the balance round: 320 rounds before the current round. Y is 1 if A had positive algos online in the agreement round, else Y is 0 and X is a type specific zero-value +- Availability: v11 +- Mode: Application + +### voter_params + +Fields + +| Index | Name | Type | Notes | +| - | ------ | -- | --------- | +| 0 | VoterBalance | uint64 | Online stake in microalgos | +| 1 | VoterIncentiveEligible | bool | Had this account opted into block payouts | + + +## online_stake + +- Bytecode: 0x75 +- Stack: ... → ..., uint64 +- the total online stake in the agreement round +- Availability: v11 +- Mode: Application + +## min_balance + +- Bytecode: 0x78 +- Stack: ..., A → ..., uint64 +- minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change. +- Availability: v3 +- Mode: Application + +params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value. + +## pushbytes + +- Syntax: `pushbytes BYTES` where BYTES: a byte constant +- Bytecode: 0x80 {varuint length, bytes} +- Stack: ... → ..., []byte +- immediate BYTES +- Availability: v3 + +pushbytes args are not added to the bytecblock during assembly processes + +## pushint + +- Syntax: `pushint UINT` where UINT: an int constant +- Bytecode: 0x81 {varuint} +- Stack: ... → ..., uint64 +- immediate UINT +- Availability: v3 + +pushint args are not added to the intcblock during assembly processes + +## pushbytess + +- Syntax: `pushbytess BYTES ...` where BYTES ...: a list of byte constants +- Bytecode: 0x82 {varuint count, [varuint length, bytes ...]} +- Stack: ... → ..., [N items] +- push sequences of immediate byte arrays to stack (first byte array being deepest) +- Availability: v8 + +pushbytess args are not added to the bytecblock during assembly processes + +## pushints + +- Syntax: `pushints UINT ...` where UINT ...: a list of int constants +- Bytecode: 0x83 {varuint count, [varuint ...]} +- Stack: ... → ..., [N items] +- push sequence of immediate uints to stack in the order they appear (first uint being deepest) +- Availability: v8 + +pushints args are not added to the intcblock during assembly processes + +## ed25519verify_bare + +- Bytecode: 0x84 +- Stack: ..., A: []byte, B: [64]byte, C: [32]byte → ..., bool +- for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} +- **Cost**: 1900 +- Availability: v7 + +## falcon_verify + +- Bytecode: 0x85 +- Stack: ..., A: []byte, B: [1232]byte, C: [1793]byte → ..., bool +- for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey => {0 or 1} +- **Cost**: 1700 +- Availability: v12 + +## callsub + +- Syntax: `callsub TARGET` where TARGET: branch offset +- Bytecode: 0x88 {int16 (big-endian)} +- Stack: ... → ... +- branch unconditionally to TARGET, saving the next instruction on the call stack +- Availability: v4 + +The call stack is separate from the data stack. Only `callsub`, `retsub`, and `proto` manipulate it. + +## retsub + +- Bytecode: 0x89 +- Stack: ... → ... +- pop the top instruction from the call stack and branch to it +- Availability: v4 + +If the current frame was prepared by `proto A R`, `retsub` will remove the 'A' arguments from the stack, move the `R` return values down, and pop any stack locations above the relocated return values. + +## proto + +- Syntax: `proto A R` where A: number of arguments, R: number of return values +- Bytecode: 0x8a {uint8}, {uint8} +- Stack: ... → ... +- Prepare top call frame for a retsub that will assume A args and R return values. +- Availability: v8 + +Fails unless the last instruction executed was a `callsub`. + +## frame_dig + +- Syntax: `frame_dig I` where I: frame slot +- Bytecode: 0x8b {int8} +- Stack: ... → ..., any +- Nth (signed) value from the frame pointer. +- Availability: v8 + +## frame_bury + +- Syntax: `frame_bury I` where I: frame slot +- Bytecode: 0x8c {int8} +- Stack: ..., A → ... +- replace the Nth (signed) value from the frame pointer in the stack with A +- Availability: v8 + +## switch + +- Syntax: `switch TARGET ...` where TARGET ...: list of labels +- Bytecode: 0x8d {varuint count, [int16 (big-endian) ...]} +- Stack: ..., A: uint64 → ... +- branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. +- Availability: v8 + +## match + +- Syntax: `match TARGET ...` where TARGET ...: list of labels +- Bytecode: 0x8e {varuint count, [int16 (big-endian) ...]} +- Stack: ..., [A1, A2, ..., AN], B → ... +- given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. +- Availability: v8 + +`match` consumes N+1 values from the stack. Let the top stack value be B. The following N values represent an ordered list of match cases/constants (A), where the first value (A[0]) is the deepest in the stack. The immediate arguments are an ordered list of N labels (T). `match` will branch to target T[I], where A[I] = B. If there are no matches then execution continues on to the next instruction. + +## shl + +- Bytecode: 0x90 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A times 2^B, modulo 2^64 +- Availability: v4 + +## shr + +- Bytecode: 0x91 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A divided by 2^B +- Availability: v4 + +## sqrt + +- Bytecode: 0x92 +- Stack: ..., A: uint64 → ..., uint64 +- The largest integer I such that I^2 <= A +- **Cost**: 4 +- Availability: v4 + +## bitlen + +- Bytecode: 0x93 +- Stack: ..., A → ..., uint64 +- The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4 +- Availability: v4 + +bitlen interprets arrays as big-endian integers, unlike setbit/getbit + +## exp + +- Bytecode: 0x94 +- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- A raised to the Bth power. Fail if A == B == 0 and on overflow +- Availability: v4 + +## expw + +- Bytecode: 0x95 +- Stack: ..., A: uint64, B: uint64 → ..., X: uint64, Y: uint64 +- A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1 +- **Cost**: 10 +- Availability: v4 + +## bsqrt + +- Bytecode: 0x96 +- Stack: ..., A: bigint → ..., bigint +- The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers +- **Cost**: 40 +- Availability: v6 + +## divw + +- Bytecode: 0x97 +- Stack: ..., A: uint64, B: uint64, C: uint64 → ..., uint64 +- A,B / C. Fail if C == 0 or if result overflows. +- Availability: v6 + +The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low. + +## sha3_256 + +- Bytecode: 0x98 +- Stack: ..., A: []byte → ..., [32]byte +- SHA3_256 hash of value A, yields [32]byte +- **Cost**: 130 +- Availability: v7 + +## b+ + +- Bytecode: 0xa0 +- Stack: ..., A: bigint, B: bigint → ..., []byte +- A plus B. A and B are interpreted as big-endian unsigned integers +- **Cost**: 10 +- Availability: v4 + +## b- + +- Bytecode: 0xa1 +- Stack: ..., A: bigint, B: bigint → ..., bigint +- A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow. +- **Cost**: 10 +- Availability: v4 + +## b/ + +- Bytecode: 0xa2 +- Stack: ..., A: bigint, B: bigint → ..., bigint +- A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero. +- **Cost**: 20 +- Availability: v4 + +## b* + +- Bytecode: 0xa3 +- Stack: ..., A: bigint, B: bigint → ..., []byte +- A times B. A and B are interpreted as big-endian unsigned integers. +- **Cost**: 20 +- Availability: v4 + +## b< + +- Bytecode: 0xa4 +- Stack: ..., A: bigint, B: bigint → ..., bool +- 1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers +- Availability: v4 + +## b> + +- Bytecode: 0xa5 +- Stack: ..., A: bigint, B: bigint → ..., bool +- 1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers +- Availability: v4 + +## b<= + +- Bytecode: 0xa6 +- Stack: ..., A: bigint, B: bigint → ..., bool +- 1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers +- Availability: v4 + +## b>= + +- Bytecode: 0xa7 +- Stack: ..., A: bigint, B: bigint → ..., bool +- 1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers +- Availability: v4 + +## b== + +- Bytecode: 0xa8 +- Stack: ..., A: bigint, B: bigint → ..., bool +- 1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers +- Availability: v4 + +## b!= + +- Bytecode: 0xa9 +- Stack: ..., A: bigint, B: bigint → ..., bool +- 0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers +- Availability: v4 + +## b% + +- Bytecode: 0xaa +- Stack: ..., A: bigint, B: bigint → ..., bigint +- A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. +- **Cost**: 20 +- Availability: v4 + +## b| + +- Bytecode: 0xab +- Stack: ..., A: []byte, B: []byte → ..., []byte +- A bitwise-or B. A and B are zero-left extended to the greater of their lengths +- **Cost**: 6 +- Availability: v4 + +## b& + +- Bytecode: 0xac +- Stack: ..., A: []byte, B: []byte → ..., []byte +- A bitwise-and B. A and B are zero-left extended to the greater of their lengths +- **Cost**: 6 +- Availability: v4 + +## b^ + +- Bytecode: 0xad +- Stack: ..., A: []byte, B: []byte → ..., []byte +- A bitwise-xor B. A and B are zero-left extended to the greater of their lengths +- **Cost**: 6 +- Availability: v4 + +## b~ + +- Bytecode: 0xae +- Stack: ..., A: []byte → ..., []byte +- A with all bits inverted +- **Cost**: 4 +- Availability: v4 + +## bzero + +- Bytecode: 0xaf +- Stack: ..., A: uint64 → ..., []byte +- zero filled byte-array of length A +- Availability: v4 + +## log + +- Bytecode: 0xb0 +- Stack: ..., A: []byte → ... +- write A to log state of the current application +- Availability: v5 +- Mode: Application + +`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes. + +## itxn_begin + +- Bytecode: 0xb1 +- Stack: ... → ... +- begin preparation of a new inner transaction in a new transaction group +- Availability: v5 +- Mode: Application + +`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the invoking transaction, and all other fields to zero or empty values. + +## itxn_field + +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) +- Bytecode: 0xb2 {uint8} +- Stack: ..., A → ... +- set field F of the current inner transaction to A +- Availability: v5 +- Mode: Application + +`itxn_field` fails if A is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if A is an account, asset, or app that is not _available_, or an attempt is made extend an array field beyond the limit imposed by consensus parameters. (Addresses set into asset params of acfg transactions need not be _available_.) + +## itxn_submit + +- Bytecode: 0xb3 +- Stack: ... → ... +- execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails. +- Availability: v5 +- Mode: Application + +`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction. + +## itxn + +- Syntax: `itxn F` where F: [txn](#field-group-txn) +- Bytecode: 0xb4 {uint8} +- Stack: ... → ..., any +- field F of the last inner transaction +- Availability: v5 +- Mode: Application + +## itxna + +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index +- Bytecode: 0xb5 {uint8}, {uint8} +- Stack: ... → ..., any +- Ith value of the array field F of the last inner transaction +- Availability: v5 +- Mode: Application + +## itxn_next + +- Bytecode: 0xb6 +- Stack: ... → ... +- begin preparation of a new inner transaction in the same transaction group +- Availability: v6 +- Mode: Application + +`itxn_next` initializes the transaction exactly as `itxn_begin` does + +## gitxn + +- Syntax: `gitxn T F` where T: transaction group index, F: [txn](#field-group-txn) +- Bytecode: 0xb7 {uint8}, {uint8} +- Stack: ... → ..., any +- field F of the Tth transaction in the last inner group submitted +- Availability: v6 +- Mode: Application + +## gitxna + +- Syntax: `gitxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0xb8 {uint8}, {uint8}, {uint8} +- Stack: ... → ..., any +- Ith value of the array field F from the Tth transaction in the last inner group submitted +- Availability: v6 +- Mode: Application + +## box_create + +- Bytecode: 0xb9 +- Stack: ..., A: boxName, B: uint64 → ..., bool +- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 +- Availability: v8 +- Mode: Application + +Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`. + +## box_extract + +- Bytecode: 0xba +- Stack: ..., A: boxName, B: uint64, C: uint64 → ..., []byte +- read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. +- Availability: v8 +- Mode: Application + +## box_replace + +- Bytecode: 0xbb +- Stack: ..., A: boxName, B: uint64, C: []byte → ... +- write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. +- Availability: v8 +- Mode: Application + +## box_del + +- Bytecode: 0xbc +- Stack: ..., A: boxName → ..., bool +- delete box named A if it exists. Return 1 if A existed, 0 otherwise +- Availability: v8 +- Mode: Application + +## box_len + +- Bytecode: 0xbd +- Stack: ..., A: boxName → ..., X: uint64, Y: bool +- X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. +- Availability: v8 +- Mode: Application + +## box_get + +- Bytecode: 0xbe +- Stack: ..., A: boxName → ..., X: []byte, Y: bool +- X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. +- Availability: v8 +- Mode: Application + +For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace` + +## box_put + +- Bytecode: 0xbf +- Stack: ..., A: boxName, B: []byte → ... +- replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist +- Availability: v8 +- Mode: Application + +For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace` + +## txnas + +- Syntax: `txnas F` where F: [txna](#field-group-txna) +- Bytecode: 0xc0 {uint8} +- Stack: ..., A: uint64 → ..., any +- Ath value of the array field F of the current transaction +- Availability: v5 + +## gtxnas + +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) +- Bytecode: 0xc1 {uint8}, {uint8} +- Stack: ..., A: uint64 → ..., any +- Ath value of the array field F from the Tth transaction in the current group +- Availability: v5 + +## gtxnsas + +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) +- Bytecode: 0xc2 {uint8} +- Stack: ..., A: uint64, B: uint64 → ..., any +- Bth value of the array field F from the Ath transaction in the current group +- Availability: v5 + +## args + +- Bytecode: 0xc3 +- Stack: ..., A: uint64 → ..., []byte +- Ath LogicSig argument +- Availability: v5 +- Mode: Signature + +## gloadss + +- Bytecode: 0xc4 +- Stack: ..., A: uint64, B: uint64 → ..., any +- Bth scratch space value of the Ath transaction in the current group +- Availability: v6 +- Mode: Application + +## itxnas + +- Syntax: `itxnas F` where F: [txna](#field-group-txna) +- Bytecode: 0xc5 {uint8} +- Stack: ..., A: uint64 → ..., any +- Ath value of the array field F of the last inner transaction +- Availability: v6 +- Mode: Application + +## gitxnas + +- Syntax: `gitxnas T F` where T: transaction group index, F: [txna](#field-group-txna) +- Bytecode: 0xc6 {uint8}, {uint8} +- Stack: ..., A: uint64 → ..., any +- Ath value of the array field F from the Tth transaction in the last inner group submitted +- Availability: v6 +- Mode: Application + +## vrf_verify + +- Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) +- Bytecode: 0xd0 {uint8} +- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: [64]byte, Y: bool +- Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. +- **Cost**: 5700 +- Availability: v7 + +### vrf_verify + +Standards + +| Index | Name | Notes | +| - | ------ | --------- | +| 0 | VrfAlgorand | | + + +`VrfAlgorand` is the VRF used in Algorand. It is ECVRF-ED25519-SHA512-Elligator2, specified in the IETF internet draft [draft-irtf-cfrg-vrf-03](https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/). + +## block + +- Syntax: `block F` where F: [block](#field-group-block) +- Bytecode: 0xd1 {uint8} +- Stack: ..., A: uint64 → ..., any +- field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) +- Availability: v7 + +### block + +Fields + +| Index | Name | Type | In | Notes | +| - | ------ | -- | - | --------- | +| 0 | BlkSeed | [32]byte | | | +| 1 | BlkTimestamp | uint64 | | | +| 2 | BlkProposer | address | v11 | | +| 3 | BlkFeesCollected | uint64 | v11 | | +| 4 | BlkBonus | uint64 | v11 | | +| 5 | BlkBranch | [32]byte | v11 | | +| 6 | BlkFeeSink | address | v11 | | +| 7 | BlkProtocol | []byte | v11 | | +| 8 | BlkTxnCounter | uint64 | v11 | | +| 9 | BlkProposerPayout | uint64 | v11 | | + + +## box_splice + +- Bytecode: 0xd2 +- Stack: ..., A: boxName, B: uint64, C: uint64, D: []byte → ... +- set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C. +- Availability: v10 +- Mode: Application + +Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed from the end. If C > len(D), zero bytes will be appended to the end to reach the box length. + +## box_resize + +- Bytecode: 0xd3 +- Stack: ..., A: boxName, B: uint64 → ... +- change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768. +- Availability: v10 +- Mode: Application + +## ec_add + +- Syntax: `ec_add G` where G: [EC](#field-group-ec) +- Bytecode: 0xe0 {uint8} +- Stack: ..., A: []byte, B: []byte → ..., []byte +- for curve points A and B, return the curve point A + B +- **Cost**: BN254g1=125; BN254g2=170; BLS12_381g1=205; BLS12_381g2=290 +- Availability: v10 + +### EC + +Groups + +| Index | Name | Notes | +| - | ------ | --------- | +| 0 | BN254g1 | G1 of the BN254 curve. Points encoded as 32 byte X following by 32 byte Y | +| 1 | BN254g2 | G2 of the BN254 curve. Points encoded as 64 byte X following by 64 byte Y | +| 2 | BLS12_381g1 | G1 of the BLS 12-381 curve. Points encoded as 48 byte X following by 48 byte Y | +| 3 | BLS12_381g2 | G2 of the BLS 12-381 curve. Points encoded as 96 byte X following by 96 byte Y | + + +A and B are curve points in affine representation: field element X concatenated with field element Y. Field element `Z` is encoded as follows. +For the base field elements (Fp), `Z` is encoded as a big-endian number and must be lower than the field modulus. +For the quadratic field extension (Fp2), `Z` is encoded as the concatenation of the individual encoding of the coefficients. For an Fp2 element of the form `Z = Z0 + Z1 i`, where `i` is a formal quadratic non-residue, the encoding of Z is the concatenation of the encoding of `Z0` and `Z1` in this order. (`Z0` and `Z1` must be less than the field modulus). + +The point at infinity is encoded as `(X,Y) = (0,0)`. +Groups G1 and G2 are denoted additively. + +Fails if A or B is not in G. +A and/or B are allowed to be the point at infinity. +Does _not_ check if A and B are in the main prime-order subgroup. + +## ec_scalar_mul + +- Syntax: `ec_scalar_mul G` where G: [EC](#field-group-ec) +- Bytecode: 0xe1 {uint8} +- Stack: ..., A: []byte, B: []byte → ..., []byte +- for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B. +- **Cost**: BN254g1=1810; BN254g2=3430; BLS12_381g1=2950; BLS12_381g2=6530 +- Availability: v10 + +A is a curve point encoded and checked as described in `ec_add`. Scalar B is interpreted as a big-endian unsigned integer. Fails if B exceeds 32 bytes. + +## ec_pairing_check + +- Syntax: `ec_pairing_check G` where G: [EC](#field-group-ec) +- Bytecode: 0xe2 {uint8} +- Stack: ..., A: []byte, B: []byte → ..., bool +- 1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0 +- **Cost**: BN254g1=8000 + 7400 per 64 bytes of B; BN254g2=8000 + 7400 per 128 bytes of B; BLS12_381g1=13000 + 10000 per 96 bytes of B; BLS12_381g2=13000 + 10000 per 192 bytes of B +- Availability: v10 + +A and B are concatenated points, encoded and checked as described in `ec_add`. A contains points of the group G, B contains points of the associated group (G2 if G is G1, and vice versa). Fails if A and B have a different number of points, or if any point is not in its described group or outside the main prime-order subgroup - a stronger condition than other opcodes. AVM values are limited to 4096 bytes, so `ec_pairing_check` is limited by the size of the points in the groups being operated upon. + +## ec_multi_scalar_mul + +- Syntax: `ec_multi_scalar_mul G` where G: [EC](#field-group-ec) +- Bytecode: 0xe3 {uint8} +- Stack: ..., A: []byte, B: []byte → ..., []byte +- for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + ... + BnAn +- **Cost**: BN254g1=3600 + 90 per 32 bytes of B; BN254g2=7200 + 270 per 32 bytes of B; BLS12_381g1=6500 + 95 per 32 bytes of B; BLS12_381g2=14850 + 485 per 32 bytes of B +- Availability: v10 + +A is a list of concatenated points, encoded and checked as described in `ec_add`. B is a list of concatenated scalars which, unlike ec_scalar_mul, must all be exactly 32 bytes long. +The name `ec_multi_scalar_mul` was chosen to reflect common usage, but a more consistent name would be `ec_multi_scalar_mul`. AVM values are limited to 4096 bytes, so `ec_multi_scalar_mul` is limited by the size of the points in the group being operated upon. + +## ec_subgroup_check + +- Syntax: `ec_subgroup_check G` where G: [EC](#field-group-ec) +- Bytecode: 0xe4 {uint8} +- Stack: ..., A: []byte → ..., bool +- 1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all. +- **Cost**: BN254g1=20; BN254g2=3100; BLS12_381g1=1850; BLS12_381g2=2340 +- Availability: v10 + +## ec_map_to + +- Syntax: `ec_map_to G` where G: [EC](#field-group-ec) +- Bytecode: 0xe5 {uint8} +- Stack: ..., A: []byte → ..., []byte +- maps field element A to group G +- **Cost**: BN254g1=630; BN254g2=3300; BLS12_381g1=1950; BLS12_381g2=8150 +- Availability: v10 + +BN254 points are mapped by the SVDW map. BLS12-381 points are mapped by the SSWU map. +G1 element inputs are base field elements and G2 element inputs are quadratic field elements, with nearly the same encoding rules (for field elements) as defined in `ec_add`. There is one difference of encoding rule: G1 element inputs do not need to be 0-padded if they fit in less than 32 bytes for BN254 and less than 48 bytes for BLS12-381. (As usual, the empty byte array represents 0.) G2 elements inputs need to be always have the required size. + +## mimc + +- Syntax: `mimc C` where C: [Mimc Configurations](#field-group-mimc configurations) +- Bytecode: 0xe6 {uint8} +- Stack: ..., A: []byte → ..., [32]byte +- MiMC hash of scalars A, using curve and parameters specified by configuration C +- **Cost**: BN254Mp110=10 + 550 per 32 bytes of A; BLS12_381Mp111=10 + 550 per 32 bytes of A +- Availability: v11 + +### Mimc Configurations + +Parameters + +| Index | Name | Notes | +| - | ------ | --------- | +| 0 | BN254Mp110 | MiMC configuration for the BN254 curve with Miyaguchi-Preneel mode, 110 rounds, exponent 5, seed "seed" | +| 1 | BLS12_381Mp111 | MiMC configuration for the BLS12-381 curve with Miyaguchi-Preneel mode, 111 rounds, exponent 5, seed "seed" | + + +A is a list of concatenated 32 byte big-endian unsigned integer scalars. Fail if A's length is not a multiple of 32 or any element exceeds the curve modulus. + +The MiMC hash function has known collisions since any input which is a multiple of the elliptic curve modulus will hash to the same value. MiMC is thus not a general purpose hash function, but meant to be used in zero knowledge applications to match a zk-circuit implementation. diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 0e543265d8..2748bd7dca 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -39,7 +39,7 @@ var opDescByName = map[string]OpDesc{ "sha3_256": {"SHA3_256 hash of value A, yields [32]byte", "", nil}, "sumhash512": {"sumhash512 of value A, yields [64]byte", "", nil}, - "falcon_verify": {"for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey", "", nil}, + "falcon_verify": {"for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey => {0 or 1}", "", nil}, "mimc": {"MiMC hash of scalars A, using curve and parameters specified by configuration C", "" + "A is a list of concatenated 32 byte big-endian unsigned integer scalars. Fail if A's length is not a multiple of 32 or any element exceeds the curve modulus.\n\n" + diff --git a/data/transactions/logic/langspec_v12.json b/data/transactions/logic/langspec_v12.json new file mode 100644 index 0000000000..1e2ebeea2a --- /dev/null +++ b/data/transactions/logic/langspec_v12.json @@ -0,0 +1,4946 @@ +{ + "Version": 12, + "LogicSigVersion": 11, + "NamedTypes": [ + { + "Name": "[]byte", + "Abbreviation": "b", + "Bound": [ + 0, + 4096 + ], + "AVMType": "[]byte" + }, + { + "Name": "address", + "Abbreviation": "A", + "Bound": [ + 32, + 32 + ], + "AVMType": "[]byte" + }, + { + "Name": "any", + "Abbreviation": "a", + "Bound": [ + 0, + 0 + ], + "AVMType": "any" + }, + { + "Name": "bigint", + "Abbreviation": "I", + "Bound": [ + 0, + 64 + ], + "AVMType": "[]byte" + }, + { + "Name": "bool", + "Abbreviation": "T", + "Bound": [ + 0, + 1 + ], + "AVMType": "uint64" + }, + { + "Name": "boxName", + "Abbreviation": "N", + "Bound": [ + 1, + 64 + ], + "AVMType": "[]byte" + }, + { + "Name": "method", + "Abbreviation": "M", + "Bound": [ + 4, + 4 + ], + "AVMType": "[]byte" + }, + { + "Name": "none", + "Abbreviation": "x", + "Bound": [ + 0, + 0 + ], + "AVMType": "none" + }, + { + "Name": "stateKey", + "Abbreviation": "K", + "Bound": [ + 0, + 64 + ], + "AVMType": "[]byte" + }, + { + "Name": "uint64", + "Abbreviation": "i", + "Bound": [ + 0, + 18446744073709551615 + ], + "AVMType": "uint64" + } + ], + "Ops": [ + { + "Opcode": 0, + "Name": "err", + "Size": 1, + "DocCost": "1", + "Doc": "Fail immediately.", + "IntroducedVersion": 1, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 1, + "Name": "sha256", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], + "Size": 1, + "DocCost": "35", + "Doc": "SHA256 hash of value A, yields [32]byte", + "IntroducedVersion": 1, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 2, + "Name": "keccak256", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], + "Size": 1, + "DocCost": "130", + "Doc": "Keccak256 hash of value A, yields [32]byte", + "IntroducedVersion": 1, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 3, + "Name": "sha512_256", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], + "Size": 1, + "DocCost": "45", + "Doc": "SHA512_256 hash of value A, yields [32]byte", + "IntroducedVersion": 1, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 4, + "Name": "ed25519verify", + "Args": [ + "[]byte", + "[64]byte", + "[32]byte" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1900", + "Doc": "for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey =\u003e {0 or 1}", + "DocExtra": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", + "IntroducedVersion": 1, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 5, + "Name": "ecdsa_verify", + "Args": [ + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" + ], + "Returns": [ + "bool" + ], + "Size": 2, + "ArgEnum": [ + "Secp256k1", + "Secp256r1" + ], + "DocCost": "Secp256k1=1700; Secp256r1=2500", + "Doc": "for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey =\u003e {0 or 1}", + "DocExtra": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "V", + "Reference": "ECDSA" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 6, + "Name": "ecdsa_pk_decompress", + "Args": [ + "[33]byte" + ], + "Returns": [ + "[32]byte", + "[32]byte" + ], + "Size": 2, + "ArgEnum": [ + "Secp256k1", + "Secp256r1" + ], + "DocCost": "Secp256k1=650; Secp256r1=2400", + "Doc": "decompress pubkey A into components X, Y", + "DocExtra": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "V", + "Reference": "ECDSA" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 7, + "Name": "ecdsa_pk_recover", + "Args": [ + "[32]byte", + "uint64", + "[32]byte", + "[32]byte" + ], + "Returns": [ + "[32]byte", + "[32]byte" + ], + "Size": 2, + "ArgEnum": [ + "Secp256k1", + "Secp256r1" + ], + "DocCost": "2000", + "Doc": "for (data A, recovery id B, signature C, D) recover a public key", + "DocExtra": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "V", + "Reference": "ECDSA" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 8, + "Name": "+", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A plus B. Fail on overflow.", + "DocExtra": "Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `addw`.", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 9, + "Name": "-", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A minus B. Fail if B \u003e A.", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 10, + "Name": "/", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A divided by B (truncated division). Fail if B == 0.", + "DocExtra": "`divmodw` is available to divide the two-element values produced by `mulw` and `addw`.", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 11, + "Name": "*", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A times B. Fail on overflow.", + "DocExtra": "Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `mulw`.", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 12, + "Name": "\u003c", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A less than B =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 13, + "Name": "\u003e", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A greater than B =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 14, + "Name": "\u003c=", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A less than or equal to B =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 15, + "Name": "\u003e=", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A greater than or equal to B =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 16, + "Name": "\u0026\u0026", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A is not zero and B is not zero =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 17, + "Name": "||", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A is not zero or B is not zero =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 18, + "Name": "==", + "Args": [ + "any", + "any" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A is equal to B =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 19, + "Name": "!=", + "Args": [ + "any", + "any" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A is not equal to B =\u003e {0 or 1}", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 20, + "Name": "!", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A == 0 yields 1; else 0", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 21, + "Name": "len", + "Args": [ + "[]byte" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "yields length of byte value A", + "IntroducedVersion": 1, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 22, + "Name": "itob", + "Args": [ + "uint64" + ], + "Returns": [ + "[8]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "converts uint64 A to big-endian byte array, always of length 8", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 23, + "Name": "btoi", + "Args": [ + "[]byte" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "converts big-endian byte array A to uint64. Fails if len(A) \u003e 8. Padded by leading 0s if len(A) \u003c 8.", + "DocExtra": "`btoi` fails if the input is longer than 8 bytes.", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 24, + "Name": "%", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A modulo B. Fail if B == 0.", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 25, + "Name": "|", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A bitwise-or B", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 26, + "Name": "\u0026", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A bitwise-and B", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 27, + "Name": "^", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A bitwise-xor B", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 28, + "Name": "~", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "bitwise invert value A", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 29, + "Name": "mulw", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low", + "IntroducedVersion": 1, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 30, + "Name": "addw", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits.", + "IntroducedVersion": 2, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 31, + "Name": "divmodw", + "Args": [ + "uint64", + "uint64", + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64", + "uint64", + "uint64" + ], + "Size": 1, + "DocCost": "20", + "Doc": "W,X = (A,B / C,D); Y,Z = (A,B modulo C,D)", + "DocExtra": "The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low.", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 32, + "Name": "intcblock", + "Size": 0, + "DocCost": "1", + "Doc": "prepare block of uint64 constants for use by intc", + "DocExtra": "`intcblock` loads following program bytes into an array of integer constants in the evaluator. These integer constants can be referred to by `intc` and `intc_*` which will push the value onto the stack. Subsequent calls to `intcblock` reset and replace the integer constants available to the script.", + "ImmediateNote": [ + { + "Comment": "a block of int constant values", + "Encoding": "varuint count, [varuint ...]", + "Name": "UINT ..." + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 33, + "Name": "intc", + "Returns": [ + "uint64" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Ith constant from intcblock", + "ImmediateNote": [ + { + "Comment": "an index in the intcblock", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 34, + "Name": "intc_0", + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 0 from intcblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 35, + "Name": "intc_1", + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 1 from intcblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 36, + "Name": "intc_2", + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 2 from intcblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 37, + "Name": "intc_3", + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 3 from intcblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 38, + "Name": "bytecblock", + "Size": 0, + "DocCost": "1", + "Doc": "prepare block of byte-array constants for use by bytec", + "DocExtra": "`bytecblock` loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by `bytec` and `bytec_*` which will push the value onto the stack. Subsequent calls to `bytecblock` reset and replace the bytes constants available to the script.", + "ImmediateNote": [ + { + "Comment": "a block of byte constant values", + "Encoding": "varuint count, [varuint length, bytes ...]", + "Name": "BYTES ..." + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 39, + "Name": "bytec", + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Ith constant from bytecblock", + "ImmediateNote": [ + { + "Comment": "an index in the bytecblock", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 40, + "Name": "bytec_0", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 0 from bytecblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 41, + "Name": "bytec_1", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 1 from bytecblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 42, + "Name": "bytec_2", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 2 from bytecblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 43, + "Name": "bytec_3", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "constant 3 from bytecblock", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 44, + "Name": "arg", + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Nth LogicSig argument", + "ImmediateNote": [ + { + "Comment": "an arg index", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 45, + "Name": "arg_0", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "LogicSig argument 0", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 46, + "Name": "arg_1", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "LogicSig argument 1", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 47, + "Name": "arg_2", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "LogicSig argument 2", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 48, + "Name": "arg_3", + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "LogicSig argument 3", + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 49, + "Name": "txn", + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "Sender", + "Fee", + "FirstValid", + "FirstValidTime", + "LastValid", + "Note", + "Lease", + "Receiver", + "Amount", + "CloseRemainderTo", + "VotePK", + "SelectionPK", + "VoteFirst", + "VoteLast", + "VoteKeyDilution", + "Type", + "TypeEnum", + "XferAsset", + "AssetAmount", + "AssetSender", + "AssetReceiver", + "AssetCloseTo", + "GroupIndex", + "TxID", + "ApplicationID", + "OnCompletion", + "ApplicationArgs", + "NumAppArgs", + "Accounts", + "NumAccounts", + "ApprovalProgram", + "ClearStateProgram", + "RekeyTo", + "ConfigAsset", + "ConfigAssetTotal", + "ConfigAssetDecimals", + "ConfigAssetDefaultFrozen", + "ConfigAssetUnitName", + "ConfigAssetName", + "ConfigAssetURL", + "ConfigAssetMetadataHash", + "ConfigAssetManager", + "ConfigAssetReserve", + "ConfigAssetFreeze", + "ConfigAssetClawback", + "FreezeAsset", + "FreezeAssetAccount", + "FreezeAssetFrozen", + "Assets", + "NumAssets", + "Applications", + "NumApplications", + "GlobalNumUint", + "GlobalNumByteSlice", + "LocalNumUint", + "LocalNumByteSlice", + "ExtraProgramPages", + "Nonparticipation", + "Logs", + "NumLogs", + "CreatedAssetID", + "CreatedApplicationID", + "LastLog", + "StateProofPK", + "ApprovalProgramPages", + "NumApprovalProgramPages", + "ClearStateProgramPages", + "NumClearStateProgramPages", + "RejectVersion" + ], + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[64]byte", + "[]byte", + "uint64", + "[]byte", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "field F of current transaction", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 50, + "Name": "global", + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "MinTxnFee", + "MinBalance", + "MaxTxnLife", + "ZeroAddress", + "GroupSize", + "LogicSigVersion", + "Round", + "LatestTimestamp", + "CurrentApplicationID", + "CreatorAddress", + "CurrentApplicationAddress", + "GroupID", + "OpcodeBudget", + "CallerApplicationID", + "CallerApplicationAddress", + "AssetCreateMinBalance", + "AssetOptInMinBalance", + "GenesisHash", + "PayoutsEnabled", + "PayoutsGoOnlineFee", + "PayoutsPercent", + "PayoutsMinBalance", + "PayoutsMaxBalance" + ], + "ArgEnumTypes": [ + "uint64", + "uint64", + "uint64", + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "address", + "address", + "[32]byte", + "uint64", + "uint64", + "address", + "uint64", + "uint64", + "[32]byte", + "bool", + "uint64", + "uint64", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "global field F", + "ImmediateNote": [ + { + "Comment": "a global field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "global" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 51, + "Name": "gtxn", + "Returns": [ + "any" + ], + "Size": 3, + "ArgEnum": [ + "Sender", + "Fee", + "FirstValid", + "FirstValidTime", + "LastValid", + "Note", + "Lease", + "Receiver", + "Amount", + "CloseRemainderTo", + "VotePK", + "SelectionPK", + "VoteFirst", + "VoteLast", + "VoteKeyDilution", + "Type", + "TypeEnum", + "XferAsset", + "AssetAmount", + "AssetSender", + "AssetReceiver", + "AssetCloseTo", + "GroupIndex", + "TxID", + "ApplicationID", + "OnCompletion", + "ApplicationArgs", + "NumAppArgs", + "Accounts", + "NumAccounts", + "ApprovalProgram", + "ClearStateProgram", + "RekeyTo", + "ConfigAsset", + "ConfigAssetTotal", + "ConfigAssetDecimals", + "ConfigAssetDefaultFrozen", + "ConfigAssetUnitName", + "ConfigAssetName", + "ConfigAssetURL", + "ConfigAssetMetadataHash", + "ConfigAssetManager", + "ConfigAssetReserve", + "ConfigAssetFreeze", + "ConfigAssetClawback", + "FreezeAsset", + "FreezeAssetAccount", + "FreezeAssetFrozen", + "Assets", + "NumAssets", + "Applications", + "NumApplications", + "GlobalNumUint", + "GlobalNumByteSlice", + "LocalNumUint", + "LocalNumByteSlice", + "ExtraProgramPages", + "Nonparticipation", + "Logs", + "NumLogs", + "CreatedAssetID", + "CreatedApplicationID", + "LastLog", + "StateProofPK", + "ApprovalProgramPages", + "NumApprovalProgramPages", + "ClearStateProgramPages", + "NumClearStateProgramPages", + "RejectVersion" + ], + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[64]byte", + "[]byte", + "uint64", + "[]byte", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "field F of the Tth transaction in the current group", + "DocExtra": "for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`.", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 52, + "Name": "load", + "Returns": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Ith scratch space value. All scratch spaces are 0 at program start.", + "ImmediateNote": [ + { + "Comment": "position in scratch space to load from", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 53, + "Name": "store", + "Args": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "store A to the Ith scratch space", + "ImmediateNote": [ + { + "Comment": "position in scratch space to store to", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 54, + "Name": "txna", + "Returns": [ + "any" + ], + "Size": 3, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ith value of the array field F of the current transaction\n`txna` can be called using `txn` with 2 immediates.", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 55, + "Name": "gtxna", + "Returns": [ + "any" + ], + "Size": 4, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ith value of the array field F from the Tth transaction in the current group\n`gtxna` can be called using `gtxn` with 3 immediates.", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 56, + "Name": "gtxns", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "Sender", + "Fee", + "FirstValid", + "FirstValidTime", + "LastValid", + "Note", + "Lease", + "Receiver", + "Amount", + "CloseRemainderTo", + "VotePK", + "SelectionPK", + "VoteFirst", + "VoteLast", + "VoteKeyDilution", + "Type", + "TypeEnum", + "XferAsset", + "AssetAmount", + "AssetSender", + "AssetReceiver", + "AssetCloseTo", + "GroupIndex", + "TxID", + "ApplicationID", + "OnCompletion", + "ApplicationArgs", + "NumAppArgs", + "Accounts", + "NumAccounts", + "ApprovalProgram", + "ClearStateProgram", + "RekeyTo", + "ConfigAsset", + "ConfigAssetTotal", + "ConfigAssetDecimals", + "ConfigAssetDefaultFrozen", + "ConfigAssetUnitName", + "ConfigAssetName", + "ConfigAssetURL", + "ConfigAssetMetadataHash", + "ConfigAssetManager", + "ConfigAssetReserve", + "ConfigAssetFreeze", + "ConfigAssetClawback", + "FreezeAsset", + "FreezeAssetAccount", + "FreezeAssetFrozen", + "Assets", + "NumAssets", + "Applications", + "NumApplications", + "GlobalNumUint", + "GlobalNumByteSlice", + "LocalNumUint", + "LocalNumByteSlice", + "ExtraProgramPages", + "Nonparticipation", + "Logs", + "NumLogs", + "CreatedAssetID", + "CreatedApplicationID", + "LastLog", + "StateProofPK", + "ApprovalProgramPages", + "NumApprovalProgramPages", + "ClearStateProgramPages", + "NumClearStateProgramPages", + "RejectVersion" + ], + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[64]byte", + "[]byte", + "uint64", + "[]byte", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "field F of the Ath transaction in the current group", + "DocExtra": "for notes on transaction fields available, see `txn`. If top of stack is _i_, `gtxns field` is equivalent to `gtxn _i_ field`. gtxns exists so that _i_ can be calculated, often based on the index of the current transaction.", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], + "IntroducedVersion": 3, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 57, + "Name": "gtxnsa", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 3, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ith value of the array field F from the Ath transaction in the current group\n`gtxnsa` can be called using `gtxns` with 2 immediates.", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 3, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 58, + "Name": "gload", + "Returns": [ + "any" + ], + "Size": 3, + "DocCost": "1", + "Doc": "Ith scratch space value of the Tth transaction in the current group", + "DocExtra": "`gload` fails unless the requested transaction is an ApplicationCall and T \u003c GroupIndex.", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "position in scratch space to load from", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 4, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 59, + "Name": "gloads", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Ith scratch space value of the Ath transaction in the current group", + "DocExtra": "`gloads` fails unless the requested transaction is an ApplicationCall and A \u003c GroupIndex.", + "ImmediateNote": [ + { + "Comment": "position in scratch space to load from", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 4, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 60, + "Name": "gaid", + "Returns": [ + "uint64" + ], + "Size": 2, + "DocCost": "1", + "Doc": "ID of the asset or application created in the Tth transaction of the current group", + "DocExtra": "`gaid` fails unless the requested transaction created an asset or application and T \u003c GroupIndex.", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + } + ], + "IntroducedVersion": 4, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 61, + "Name": "gaids", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "ID of the asset or application created in the Ath transaction of the current group", + "DocExtra": "`gaids` fails unless the requested transaction created an asset or application and A \u003c GroupIndex.", + "IntroducedVersion": 4, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 62, + "Name": "loads", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Ath scratch space value. All scratch spaces are 0 at program start.", + "IntroducedVersion": 5, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 63, + "Name": "stores", + "Args": [ + "uint64", + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "store B to the Ath scratch space", + "IntroducedVersion": 5, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 64, + "Name": "bnz", + "Args": [ + "uint64" + ], + "Size": 3, + "DocCost": "1", + "Doc": "branch to TARGET if value A is not zero", + "DocExtra": "The `bnz` instruction opcode 0x40 is followed by two immediate data bytes which are a high byte first and low byte second which together form a 16 bit offset which the instruction may branch to. For a bnz instruction at `pc`, if the last element of the stack is not zero then branch to instruction at `pc + 3 + N`, else proceed to next instruction at `pc + 3`. Branch targets must be aligned instructions. (e.g. Branching to the second byte of a 2 byte op will be rejected.) Starting at v4, the offset is treated as a signed 16 bit integer allowing for backward branches and looping. In prior version (v1 to v3), branch offsets are limited to forward branches only, 0-0x7fff.\n\nAt v2 it became allowed to branch to the end of the program exactly after the last instruction: bnz to byte N (with 0-indexing) was illegal for a TEAL program with N bytes before v2, and is legal after it. This change eliminates the need for a last instruction of no-op as a branch target at the end. (Branching beyond the end--in other words, to a byte larger than N--is still illegal and will cause the program to fail.)", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], + "IntroducedVersion": 1, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 65, + "Name": "bz", + "Args": [ + "uint64" + ], + "Size": 3, + "DocCost": "1", + "Doc": "branch to TARGET if value A is zero", + "DocExtra": "See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`.", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 66, + "Name": "b", + "Size": 3, + "DocCost": "1", + "Doc": "branch unconditionally to TARGET", + "DocExtra": "See `bnz` for details on how branches work. `b` always jumps to the offset.", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 67, + "Name": "return", + "Args": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "use A as success value; end", + "IntroducedVersion": 2, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 68, + "Name": "assert", + "Args": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "immediately fail unless A is a non-zero number", + "IntroducedVersion": 3, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 69, + "Name": "bury", + "Args": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "replace the Nth value from the top of the stack with A. bury 0 fails.", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 70, + "Name": "popn", + "Size": 2, + "DocCost": "1", + "Doc": "remove N values from the top of the stack", + "ImmediateNote": [ + { + "Comment": "stack depth", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 71, + "Name": "dupn", + "Args": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "duplicate A, N times", + "ImmediateNote": [ + { + "Comment": "copy count", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 72, + "Name": "pop", + "Args": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "discard A", + "IntroducedVersion": 1, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 73, + "Name": "dup", + "Args": [ + "any" + ], + "Returns": [ + "any", + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "duplicate A", + "IntroducedVersion": 1, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 74, + "Name": "dup2", + "Args": [ + "any", + "any" + ], + "Returns": [ + "any", + "any", + "any", + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "duplicate A and B", + "IntroducedVersion": 2, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 75, + "Name": "dig", + "Args": [ + "any" + ], + "Returns": [ + "any", + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Nth value from the top of the stack. dig 0 is equivalent to dup", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 3, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 76, + "Name": "swap", + "Args": [ + "any", + "any" + ], + "Returns": [ + "any", + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "swaps A and B on stack", + "IntroducedVersion": 3, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 77, + "Name": "select", + "Args": [ + "any", + "any", + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "selects one of two values based on top-of-stack: B if C != 0, else A", + "IntroducedVersion": 3, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 78, + "Name": "cover", + "Args": [ + "any" + ], + "Returns": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth \u003c= N.", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 79, + "Name": "uncover", + "Args": [ + "any" + ], + "Returns": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth \u003c= N.", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 80, + "Name": "concat", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "join A and B", + "DocExtra": "`concat` fails if the result would be greater than 4096 bytes.", + "IntroducedVersion": 2, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 81, + "Name": "substring", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 3, + "DocCost": "1", + "Doc": "A range of bytes from A starting at S up to but not including E. If E \u003c S, or either is larger than the array length, the program fails", + "ImmediateNote": [ + { + "Comment": "start position", + "Encoding": "uint8", + "Name": "S" + }, + { + "Comment": "end position", + "Encoding": "uint8", + "Name": "E" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 82, + "Name": "substring3", + "Args": [ + "[]byte", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A range of bytes from A starting at B up to but not including C. If C \u003c B, or either is larger than the array length, the program fails", + "IntroducedVersion": 2, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 83, + "Name": "getbit", + "Args": [ + "any", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails", + "DocExtra": "see explanation of bit ordering in setbit", + "IntroducedVersion": 3, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 84, + "Name": "setbit", + "Args": [ + "any", + "uint64", + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails", + "DocExtra": "When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10.", + "IntroducedVersion": 3, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 85, + "Name": "getbyte", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails", + "IntroducedVersion": 3, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 86, + "Name": "setbyte", + "Args": [ + "[]byte", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails", + "IntroducedVersion": 3, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 87, + "Name": "extract", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 3, + "DocCost": "1", + "Doc": "A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails", + "ImmediateNote": [ + { + "Comment": "start position", + "Encoding": "uint8", + "Name": "S" + }, + { + "Comment": "length", + "Encoding": "uint8", + "Name": "L" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 88, + "Name": "extract3", + "Args": [ + "[]byte", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails\n`extract3` can be called using `extract` with no immediates.", + "IntroducedVersion": 5, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 89, + "Name": "extract_uint16", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails", + "IntroducedVersion": 5, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 90, + "Name": "extract_uint32", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails", + "IntroducedVersion": 5, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 91, + "Name": "extract_uint64", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails", + "IntroducedVersion": 5, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 92, + "Name": "replace2", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)\n`replace2` can be called using `replace` with 1 immediate.", + "ImmediateNote": [ + { + "Comment": "start position", + "Encoding": "uint8", + "Name": "S" + } + ], + "IntroducedVersion": 7, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 93, + "Name": "replace3", + "Args": [ + "[]byte", + "uint64", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)\n`replace3` can be called using `replace` with no immediates.", + "IntroducedVersion": 7, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 94, + "Name": "base64_decode", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 2, + "ArgEnum": [ + "URLEncoding", + "StdEncoding" + ], + "ArgEnumTypes": [ + "any", + "any" + ], + "DocCost": "1 + 1 per 16 bytes of A", + "Doc": "decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E", + "DocExtra": "*Warning*: Usage should be restricted to very rare use cases. In almost all cases, smart contracts should directly handle non-encoded byte-strings.\tThis opcode should only be used in cases where base64 is the only available option, e.g. interoperability with a third-party that only signs base64 strings.\n\n Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See [RFC 4648 sections 4 and 5](https://rfc-editor.org/rfc/rfc4648.html#section-4). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\\n` and `\\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\\r`, or `\\n`.", + "ImmediateNote": [ + { + "Comment": "encoding index", + "Encoding": "uint8", + "Name": "E", + "Reference": "base64" + } + ], + "IntroducedVersion": 7, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 95, + "Name": "json_ref", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "JSONString", + "JSONUint64", + "JSONObject" + ], + "ArgEnumTypes": [ + "[]byte", + "uint64", + "[]byte" + ], + "DocCost": "25 + 2 per 7 bytes of A", + "Doc": "key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A", + "DocExtra": "*Warning*: Usage should be restricted to very rare use cases, as JSON decoding is expensive and quite limited. In addition, JSON objects are large and not optimized for size.\n\nAlmost all smart contracts should use simpler and smaller methods (such as the [ABI](https://arc.algorand.foundation/ARCs/arc-0004). This opcode should only be used in cases where JSON is only available option, e.g. when a third-party only signs JSON.", + "ImmediateNote": [ + { + "Comment": "return type index", + "Encoding": "uint8", + "Name": "R", + "Reference": "json_ref" + } + ], + "IntroducedVersion": 7, + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 96, + "Name": "balance", + "Args": [ + "any" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following `itxn_submit`", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 97, + "Name": "app_opted_in", + "Args": [ + "any", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "1 if account A is opted in to application B, else 0", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 98, + "Name": "app_local_get", + "Args": [ + "any", + "stateKey" + ], + "Returns": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "local state of the key B in the current application in account A", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key. Return: value. The value is zero (of type uint64) if the key does not exist.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 99, + "Name": "app_local_get_ex", + "Args": [ + "any", + "uint64", + "stateKey" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "X is the local state of application B, key C in account A. Y is 1 if key existed, else 0", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 100, + "Name": "app_global_get", + "Args": [ + "stateKey" + ], + "Returns": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "global state of the key A in the current application", + "DocExtra": "params: state key. Return: value. The value is zero (of type uint64) if the key does not exist.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 101, + "Name": "app_global_get_ex", + "Args": [ + "uint64", + "stateKey" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "X is the global state of application A, key B. Y is 1 if key existed, else 0", + "DocExtra": "params: Txn.ForeignApps offset (or, since v4, an _available_ application id), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 102, + "Name": "app_local_put", + "Args": [ + "any", + "stateKey", + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "write C to key B in account A's local state of the current application", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key, value.", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 103, + "Name": "app_global_put", + "Args": [ + "stateKey", + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "write B to key A in the global state of the current application", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 104, + "Name": "app_local_del", + "Args": [ + "any", + "stateKey" + ], + "Size": 1, + "DocCost": "1", + "Doc": "delete key B from account A's local state of the current application", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key.\n\nDeleting a key which is already absent has no effect on the application local state. (In particular, it does _not_ cause the program to fail.)", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 105, + "Name": "app_global_del", + "Args": [ + "stateKey" + ], + "Size": 1, + "DocCost": "1", + "Doc": "delete key A from the global state of the current application", + "DocExtra": "params: state key.\n\nDeleting a key which is already absent has no effect on the application global state. (In particular, it does _not_ cause the program to fail.)", + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 112, + "Name": "asset_holding_get", + "Args": [ + "any", + "uint64" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 2, + "ArgEnum": [ + "AssetBalance", + "AssetFrozen" + ], + "ArgEnumTypes": [ + "uint64", + "bool" + ], + "DocCost": "1", + "Doc": "X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", + "ImmediateNote": [ + { + "Comment": "asset holding field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "asset_holding" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 113, + "Name": "asset_params_get", + "Args": [ + "uint64" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 2, + "ArgEnum": [ + "AssetTotal", + "AssetDecimals", + "AssetDefaultFrozen", + "AssetUnitName", + "AssetName", + "AssetURL", + "AssetMetadataHash", + "AssetManager", + "AssetReserve", + "AssetFreeze", + "AssetClawback", + "AssetCreator" + ], + "ArgEnumTypes": [ + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "address" + ], + "DocCost": "1", + "Doc": "X is field F from asset A. Y is 1 if A exists, else 0", + "DocExtra": "params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", + "ImmediateNote": [ + { + "Comment": "asset params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "asset_params" + } + ], + "IntroducedVersion": 2, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 114, + "Name": "app_params_get", + "Args": [ + "uint64" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 2, + "ArgEnum": [ + "AppApprovalProgram", + "AppClearStateProgram", + "AppGlobalNumUint", + "AppGlobalNumByteSlice", + "AppLocalNumUint", + "AppLocalNumByteSlice", + "AppExtraProgramPages", + "AppCreator", + "AppAddress", + "AppVersion" + ], + "ArgEnumTypes": [ + "[]byte", + "[]byte", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "address", + "address", + "uint64" + ], + "DocCost": "1", + "Doc": "X is field F from app A. Y is 1 if A exists, else 0", + "DocExtra": "params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value.", + "ImmediateNote": [ + { + "Comment": "app params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "app_params" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 115, + "Name": "acct_params_get", + "Args": [ + "any" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 2, + "ArgEnum": [ + "AcctBalance", + "AcctMinBalance", + "AcctAuthAddr", + "AcctTotalNumUint", + "AcctTotalNumByteSlice", + "AcctTotalExtraAppPages", + "AcctTotalAppsCreated", + "AcctTotalAppsOptedIn", + "AcctTotalAssetsCreated", + "AcctTotalAssets", + "AcctTotalBoxes", + "AcctTotalBoxBytes", + "AcctIncentiveEligible", + "AcctLastProposed", + "AcctLastHeartbeat" + ], + "ArgEnumTypes": [ + "uint64", + "uint64", + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "X is field F from account A. Y is 1 if A owns positive algos, else 0", + "ImmediateNote": [ + { + "Comment": "account params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "acct_params" + } + ], + "IntroducedVersion": 6, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 116, + "Name": "voter_params_get", + "Args": [ + "any" + ], + "Returns": [ + "any", + "bool" + ], + "Size": 2, + "DocCost": "1", + "Doc": "X is field F from online account A as of the balance round: 320 rounds before the current round. Y is 1 if A had positive algos online in the agreement round, else Y is 0 and X is a type specific zero-value", + "ImmediateNote": [ + { + "Comment": "voter params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "voter_params" + } + ], + "IntroducedVersion": 11, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 117, + "Name": "online_stake", + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "the total online stake in the agreement round", + "IntroducedVersion": 11, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 120, + "Name": "min_balance", + "Args": [ + "any" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change.", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "IntroducedVersion": 3, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 128, + "Name": "pushbytes", + "Returns": [ + "[]byte" + ], + "Size": 0, + "DocCost": "1", + "Doc": "immediate BYTES", + "DocExtra": "pushbytes args are not added to the bytecblock during assembly processes", + "ImmediateNote": [ + { + "Comment": "a byte constant", + "Encoding": "varuint length, bytes", + "Name": "BYTES" + } + ], + "IntroducedVersion": 3, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 129, + "Name": "pushint", + "Returns": [ + "uint64" + ], + "Size": 0, + "DocCost": "1", + "Doc": "immediate UINT", + "DocExtra": "pushint args are not added to the intcblock during assembly processes", + "ImmediateNote": [ + { + "Comment": "an int constant", + "Encoding": "varuint", + "Name": "UINT" + } + ], + "IntroducedVersion": 3, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 130, + "Name": "pushbytess", + "Size": 0, + "DocCost": "1", + "Doc": "push sequences of immediate byte arrays to stack (first byte array being deepest)", + "DocExtra": "pushbytess args are not added to the bytecblock during assembly processes", + "ImmediateNote": [ + { + "Comment": "a list of byte constants", + "Encoding": "varuint count, [varuint length, bytes ...]", + "Name": "BYTES ..." + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 131, + "Name": "pushints", + "Size": 0, + "DocCost": "1", + "Doc": "push sequence of immediate uints to stack in the order they appear (first uint being deepest)", + "DocExtra": "pushints args are not added to the intcblock during assembly processes", + "ImmediateNote": [ + { + "Comment": "a list of int constants", + "Encoding": "varuint count, [varuint ...]", + "Name": "UINT ..." + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 132, + "Name": "ed25519verify_bare", + "Args": [ + "[]byte", + "[64]byte", + "[32]byte" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1900", + "Doc": "for (data A, signature B, pubkey C) verify the signature of the data against the pubkey =\u003e {0 or 1}", + "IntroducedVersion": 7, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 133, + "Name": "falcon_verify", + "Args": [ + "[]byte", + "[1232]byte", + "[1793]byte" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1700", + "Doc": "for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey =\u003e {0 or 1}", + "IntroducedVersion": 12, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 136, + "Name": "callsub", + "Size": 3, + "DocCost": "1", + "Doc": "branch unconditionally to TARGET, saving the next instruction on the call stack", + "DocExtra": "The call stack is separate from the data stack. Only `callsub`, `retsub`, and `proto` manipulate it.", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], + "IntroducedVersion": 4, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 137, + "Name": "retsub", + "Size": 1, + "DocCost": "1", + "Doc": "pop the top instruction from the call stack and branch to it", + "DocExtra": "If the current frame was prepared by `proto A R`, `retsub` will remove the 'A' arguments from the stack, move the `R` return values down, and pop any stack locations above the relocated return values.", + "IntroducedVersion": 4, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 138, + "Name": "proto", + "Size": 3, + "DocCost": "1", + "Doc": "Prepare top call frame for a retsub that will assume A args and R return values.", + "DocExtra": "Fails unless the last instruction executed was a `callsub`.", + "ImmediateNote": [ + { + "Comment": "number of arguments", + "Encoding": "uint8", + "Name": "A" + }, + { + "Comment": "number of return values", + "Encoding": "uint8", + "Name": "R" + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 139, + "Name": "frame_dig", + "Returns": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Nth (signed) value from the frame pointer.", + "ImmediateNote": [ + { + "Comment": "frame slot", + "Encoding": "int8", + "Name": "I" + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 140, + "Name": "frame_bury", + "Args": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "replace the Nth (signed) value from the frame pointer in the stack with A", + "ImmediateNote": [ + { + "Comment": "frame slot", + "Encoding": "int8", + "Name": "I" + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 141, + "Name": "switch", + "Args": [ + "uint64" + ], + "Size": 0, + "DocCost": "1", + "Doc": "branch to the Ath label. Continue at following instruction if index A exceeds the number of labels.", + "ImmediateNote": [ + { + "Comment": "list of labels", + "Encoding": "varuint count, [int16 (big-endian) ...]", + "Name": "TARGET ..." + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 142, + "Name": "match", + "Size": 0, + "DocCost": "1", + "Doc": "given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found.", + "DocExtra": "`match` consumes N+1 values from the stack. Let the top stack value be B. The following N values represent an ordered list of match cases/constants (A), where the first value (A[0]) is the deepest in the stack. The immediate arguments are an ordered list of N labels (T). `match` will branch to target T[I], where A[I] = B. If there are no matches then execution continues on to the next instruction.", + "ImmediateNote": [ + { + "Comment": "list of labels", + "Encoding": "varuint count, [int16 (big-endian) ...]", + "Name": "TARGET ..." + } + ], + "IntroducedVersion": 8, + "Groups": [ + "Flow Control" + ] + }, + { + "Opcode": 144, + "Name": "shl", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A times 2^B, modulo 2^64", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 145, + "Name": "shr", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A divided by 2^B", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 146, + "Name": "sqrt", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "4", + "Doc": "The largest integer I such that I^2 \u003c= A", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 147, + "Name": "bitlen", + "Args": [ + "any" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4", + "DocExtra": "bitlen interprets arrays as big-endian integers, unlike setbit/getbit", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 148, + "Name": "exp", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A raised to the Bth power. Fail if A == B == 0 and on overflow", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 149, + "Name": "expw", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64" + ], + "Size": 1, + "DocCost": "10", + "Doc": "A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1", + "IntroducedVersion": 4, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 150, + "Name": "bsqrt", + "Args": [ + "bigint" + ], + "Returns": [ + "bigint" + ], + "Size": 1, + "DocCost": "40", + "Doc": "The largest integer I such that I^2 \u003c= A. A and I are interpreted as big-endian unsigned integers", + "IntroducedVersion": 6, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 151, + "Name": "divw", + "Args": [ + "uint64", + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "A,B / C. Fail if C == 0 or if result overflows.", + "DocExtra": "The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low.", + "IntroducedVersion": 6, + "Groups": [ + "Arithmetic" + ] + }, + { + "Opcode": 152, + "Name": "sha3_256", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], + "Size": 1, + "DocCost": "130", + "Doc": "SHA3_256 hash of value A, yields [32]byte", + "IntroducedVersion": 7, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 160, + "Name": "b+", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "10", + "Doc": "A plus B. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 161, + "Name": "b-", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bigint" + ], + "Size": 1, + "DocCost": "10", + "Doc": "A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow.", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 162, + "Name": "b/", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bigint" + ], + "Size": 1, + "DocCost": "20", + "Doc": "A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero.", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 163, + "Name": "b*", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "20", + "Doc": "A times B. A and B are interpreted as big-endian unsigned integers.", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 164, + "Name": "b\u003c", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 165, + "Name": "b\u003e", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 166, + "Name": "b\u003c=", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 167, + "Name": "b\u003e=", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 168, + "Name": "b==", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 169, + "Name": "b!=", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 170, + "Name": "b%", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bigint" + ], + "Size": 1, + "DocCost": "20", + "Doc": "A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero.", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Arithmetic" + ] + }, + { + "Opcode": 171, + "Name": "b|", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "6", + "Doc": "A bitwise-or B. A and B are zero-left extended to the greater of their lengths", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Logic" + ] + }, + { + "Opcode": 172, + "Name": "b\u0026", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "6", + "Doc": "A bitwise-and B. A and B are zero-left extended to the greater of their lengths", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Logic" + ] + }, + { + "Opcode": 173, + "Name": "b^", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "6", + "Doc": "A bitwise-xor B. A and B are zero-left extended to the greater of their lengths", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Logic" + ] + }, + { + "Opcode": 174, + "Name": "b~", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "4", + "Doc": "A with all bits inverted", + "IntroducedVersion": 4, + "Groups": [ + "Byte Array Logic" + ] + }, + { + "Opcode": 175, + "Name": "bzero", + "Args": [ + "uint64" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "zero filled byte-array of length A", + "IntroducedVersion": 4, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 176, + "Name": "log", + "Args": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "write A to log state of the current application", + "DocExtra": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", + "IntroducedVersion": 5, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 177, + "Name": "itxn_begin", + "Size": 1, + "DocCost": "1", + "Doc": "begin preparation of a new inner transaction in a new transaction group", + "DocExtra": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the invoking transaction, and all other fields to zero or empty values.", + "IntroducedVersion": 5, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 178, + "Name": "itxn_field", + "Args": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "Sender", + "Fee", + "Note", + "Receiver", + "Amount", + "CloseRemainderTo", + "VotePK", + "SelectionPK", + "VoteFirst", + "VoteLast", + "VoteKeyDilution", + "Type", + "TypeEnum", + "XferAsset", + "AssetAmount", + "AssetSender", + "AssetReceiver", + "AssetCloseTo", + "ApplicationID", + "OnCompletion", + "ApplicationArgs", + "Accounts", + "ApprovalProgram", + "ClearStateProgram", + "RekeyTo", + "ConfigAsset", + "ConfigAssetTotal", + "ConfigAssetDecimals", + "ConfigAssetDefaultFrozen", + "ConfigAssetUnitName", + "ConfigAssetName", + "ConfigAssetURL", + "ConfigAssetMetadataHash", + "ConfigAssetManager", + "ConfigAssetReserve", + "ConfigAssetFreeze", + "ConfigAssetClawback", + "FreezeAsset", + "FreezeAssetAccount", + "FreezeAssetFrozen", + "Assets", + "Applications", + "GlobalNumUint", + "GlobalNumByteSlice", + "LocalNumUint", + "LocalNumByteSlice", + "ExtraProgramPages", + "Nonparticipation", + "StateProofPK", + "ApprovalProgramPages", + "ClearStateProgramPages", + "RejectVersion" + ], + "ArgEnumTypes": [ + "address", + "uint64", + "[]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "uint64", + "[]byte", + "address", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[64]byte", + "[]byte", + "[]byte", + "uint64" + ], + "DocCost": "1", + "Doc": "set field F of the current inner transaction to A", + "DocExtra": "`itxn_field` fails if A is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if A is an account, asset, or app that is not _available_, or an attempt is made extend an array field beyond the limit imposed by consensus parameters. (Addresses set into asset params of acfg transactions need not be _available_.)", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 179, + "Name": "itxn_submit", + "Size": 1, + "DocCost": "1", + "Doc": "execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.", + "DocExtra": "`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.", + "IntroducedVersion": 5, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 180, + "Name": "itxn", + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "Sender", + "Fee", + "FirstValid", + "FirstValidTime", + "LastValid", + "Note", + "Lease", + "Receiver", + "Amount", + "CloseRemainderTo", + "VotePK", + "SelectionPK", + "VoteFirst", + "VoteLast", + "VoteKeyDilution", + "Type", + "TypeEnum", + "XferAsset", + "AssetAmount", + "AssetSender", + "AssetReceiver", + "AssetCloseTo", + "GroupIndex", + "TxID", + "ApplicationID", + "OnCompletion", + "ApplicationArgs", + "NumAppArgs", + "Accounts", + "NumAccounts", + "ApprovalProgram", + "ClearStateProgram", + "RekeyTo", + "ConfigAsset", + "ConfigAssetTotal", + "ConfigAssetDecimals", + "ConfigAssetDefaultFrozen", + "ConfigAssetUnitName", + "ConfigAssetName", + "ConfigAssetURL", + "ConfigAssetMetadataHash", + "ConfigAssetManager", + "ConfigAssetReserve", + "ConfigAssetFreeze", + "ConfigAssetClawback", + "FreezeAsset", + "FreezeAssetAccount", + "FreezeAssetFrozen", + "Assets", + "NumAssets", + "Applications", + "NumApplications", + "GlobalNumUint", + "GlobalNumByteSlice", + "LocalNumUint", + "LocalNumByteSlice", + "ExtraProgramPages", + "Nonparticipation", + "Logs", + "NumLogs", + "CreatedAssetID", + "CreatedApplicationID", + "LastLog", + "StateProofPK", + "ApprovalProgramPages", + "NumApprovalProgramPages", + "ClearStateProgramPages", + "NumClearStateProgramPages", + "RejectVersion" + ], + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[64]byte", + "[]byte", + "uint64", + "[]byte", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "field F of the last inner transaction", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 181, + "Name": "itxna", + "Returns": [ + "any" + ], + "Size": 3, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ith value of the array field F of the last inner transaction", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "a transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 182, + "Name": "itxn_next", + "Size": 1, + "DocCost": "1", + "Doc": "begin preparation of a new inner transaction in the same transaction group", + "DocExtra": "`itxn_next` initializes the transaction exactly as `itxn_begin` does", + "IntroducedVersion": 6, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 183, + "Name": "gitxn", + "Returns": [ + "any" + ], + "Size": 3, + "ArgEnum": [ + "Sender", + "Fee", + "FirstValid", + "FirstValidTime", + "LastValid", + "Note", + "Lease", + "Receiver", + "Amount", + "CloseRemainderTo", + "VotePK", + "SelectionPK", + "VoteFirst", + "VoteLast", + "VoteKeyDilution", + "Type", + "TypeEnum", + "XferAsset", + "AssetAmount", + "AssetSender", + "AssetReceiver", + "AssetCloseTo", + "GroupIndex", + "TxID", + "ApplicationID", + "OnCompletion", + "ApplicationArgs", + "NumAppArgs", + "Accounts", + "NumAccounts", + "ApprovalProgram", + "ClearStateProgram", + "RekeyTo", + "ConfigAsset", + "ConfigAssetTotal", + "ConfigAssetDecimals", + "ConfigAssetDefaultFrozen", + "ConfigAssetUnitName", + "ConfigAssetName", + "ConfigAssetURL", + "ConfigAssetMetadataHash", + "ConfigAssetManager", + "ConfigAssetReserve", + "ConfigAssetFreeze", + "ConfigAssetClawback", + "FreezeAsset", + "FreezeAssetAccount", + "FreezeAssetFrozen", + "Assets", + "NumAssets", + "Applications", + "NumApplications", + "GlobalNumUint", + "GlobalNumByteSlice", + "LocalNumUint", + "LocalNumByteSlice", + "ExtraProgramPages", + "Nonparticipation", + "Logs", + "NumLogs", + "CreatedAssetID", + "CreatedApplicationID", + "LastLog", + "StateProofPK", + "ApprovalProgramPages", + "NumApprovalProgramPages", + "ClearStateProgramPages", + "NumClearStateProgramPages", + "RejectVersion" + ], + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[64]byte", + "[]byte", + "uint64", + "[]byte", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "field F of the Tth transaction in the last inner group submitted", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], + "IntroducedVersion": 6, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 184, + "Name": "gitxna", + "Returns": [ + "any" + ], + "Size": 4, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ith value of the array field F from the Tth transaction in the last inner group submitted", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], + "IntroducedVersion": 6, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 185, + "Name": "box_create", + "Args": [ + "boxName", + "uint64" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", + "DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 186, + "Name": "box_extract", + "Args": [ + "boxName", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 187, + "Name": "box_replace", + "Args": [ + "boxName", + "uint64", + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 188, + "Name": "box_del", + "Args": [ + "boxName" + ], + "Returns": [ + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "delete box named A if it exists. Return 1 if A existed, 0 otherwise", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 189, + "Name": "box_len", + "Args": [ + "boxName" + ], + "Returns": [ + "uint64", + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 190, + "Name": "box_get", + "Args": [ + "boxName" + ], + "Returns": [ + "[]byte", + "bool" + ], + "Size": 1, + "DocCost": "1", + "Doc": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0.", + "DocExtra": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 191, + "Name": "box_put", + "Args": [ + "boxName", + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist", + "DocExtra": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", + "IntroducedVersion": 8, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 192, + "Name": "txnas", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ath value of the array field F of the current transaction", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 193, + "Name": "gtxnas", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 3, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Ath value of the array field F from the Tth transaction in the current group", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 194, + "Name": "gtxnsas", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "ApplicationArgs", + "Accounts", + "Assets", + "Applications", + "Logs", + "ApprovalProgramPages", + "ClearStateProgramPages" + ], + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], + "DocCost": "1", + "Doc": "Bth value of the array field F from the Ath transaction in the current group", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], + "IntroducedVersion": 5, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 195, + "Name": "args", + "Args": [ + "uint64" + ], + "Returns": [ + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Ath LogicSig argument", + "IntroducedVersion": 5, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 196, + "Name": "gloadss", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 1, + "DocCost": "1", + "Doc": "Bth scratch space value of the Ath transaction in the current group", + "IntroducedVersion": 6, + "Groups": [ + "Loading Values" + ] + }, + { + "Opcode": 197, + "Name": "itxnas", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 2, + "DocCost": "1", + "Doc": "Ath value of the array field F of the last inner transaction", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], + "IntroducedVersion": 6, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 198, + "Name": "gitxnas", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 3, + "DocCost": "1", + "Doc": "Ath value of the array field F from the Tth transaction in the last inner group submitted", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], + "IntroducedVersion": 6, + "Groups": [ + "Inner Transactions" + ] + }, + { + "Opcode": 208, + "Name": "vrf_verify", + "Args": [ + "[]byte", + "[80]byte", + "[32]byte" + ], + "Returns": [ + "[64]byte", + "bool" + ], + "Size": 2, + "ArgEnum": [ + "VrfAlgorand" + ], + "DocCost": "5700", + "Doc": "Verify the proof B of message A against pubkey C. Returns vrf output and verification flag.", + "DocExtra": "`VrfAlgorand` is the VRF used in Algorand. It is ECVRF-ED25519-SHA512-Elligator2, specified in the IETF internet draft [draft-irtf-cfrg-vrf-03](https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/).", + "ImmediateNote": [ + { + "Comment": " parameters index", + "Encoding": "uint8", + "Name": "S", + "Reference": "vrf_verify" + } + ], + "IntroducedVersion": 7, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 209, + "Name": "block", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], + "Size": 2, + "ArgEnum": [ + "BlkSeed", + "BlkTimestamp", + "BlkProposer", + "BlkFeesCollected", + "BlkBonus", + "BlkBranch", + "BlkFeeSink", + "BlkProtocol", + "BlkTxnCounter", + "BlkProposerPayout" + ], + "ArgEnumTypes": [ + "[32]byte", + "uint64", + "address", + "uint64", + "uint64", + "[32]byte", + "address", + "[]byte", + "uint64", + "uint64" + ], + "DocCost": "1", + "Doc": "field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)", + "ImmediateNote": [ + { + "Comment": " block field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "block" + } + ], + "IntroducedVersion": 7, + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 210, + "Name": "box_splice", + "Args": [ + "boxName", + "uint64", + "uint64", + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.", + "DocExtra": "Boxes are of constant length. If C \u003c len(D), then len(D)-C bytes will be removed from the end. If C \u003e len(D), zero bytes will be appended to the end to reach the box length.", + "IntroducedVersion": 10, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 211, + "Name": "box_resize", + "Args": [ + "boxName", + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.", + "IntroducedVersion": 10, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 224, + "Name": "ec_add", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "BN254g1=125; BN254g2=170; BLS12_381g1=205; BLS12_381g2=290", + "Doc": "for curve points A and B, return the curve point A + B", + "DocExtra": "A and B are curve points in affine representation: field element X concatenated with field element Y. Field element `Z` is encoded as follows.\nFor the base field elements (Fp), `Z` is encoded as a big-endian number and must be lower than the field modulus.\nFor the quadratic field extension (Fp2), `Z` is encoded as the concatenation of the individual encoding of the coefficients. For an Fp2 element of the form `Z = Z0 + Z1 i`, where `i` is a formal quadratic non-residue, the encoding of Z is the concatenation of the encoding of `Z0` and `Z1` in this order. (`Z0` and `Z1` must be less than the field modulus).\n\nThe point at infinity is encoded as `(X,Y) = (0,0)`.\nGroups G1 and G2 are denoted additively.\n\nFails if A or B is not in G.\nA and/or B are allowed to be the point at infinity.\nDoes _not_ check if A and B are in the main prime-order subgroup.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "G", + "Reference": "EC" + } + ], + "IntroducedVersion": 10, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 225, + "Name": "ec_scalar_mul", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "BN254g1=1810; BN254g2=3430; BLS12_381g1=2950; BLS12_381g2=6530", + "Doc": "for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B.", + "DocExtra": "A is a curve point encoded and checked as described in `ec_add`. Scalar B is interpreted as a big-endian unsigned integer. Fails if B exceeds 32 bytes.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "G", + "Reference": "EC" + } + ], + "IntroducedVersion": 10, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 226, + "Name": "ec_pairing_check", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "bool" + ], + "Size": 2, + "DocCost": "BN254g1=8000 + 7400 per 64 bytes of B; BN254g2=8000 + 7400 per 128 bytes of B; BLS12_381g1=13000 + 10000 per 96 bytes of B; BLS12_381g2=13000 + 10000 per 192 bytes of B", + "Doc": "1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0", + "DocExtra": "A and B are concatenated points, encoded and checked as described in `ec_add`. A contains points of the group G, B contains points of the associated group (G2 if G is G1, and vice versa). Fails if A and B have a different number of points, or if any point is not in its described group or outside the main prime-order subgroup - a stronger condition than other opcodes. AVM values are limited to 4096 bytes, so `ec_pairing_check` is limited by the size of the points in the groups being operated upon.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "G", + "Reference": "EC" + } + ], + "IntroducedVersion": 10, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 227, + "Name": "ec_multi_scalar_mul", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "BN254g1=3600 + 90 per 32 bytes of B; BN254g2=7200 + 270 per 32 bytes of B; BLS12_381g1=6500 + 95 per 32 bytes of B; BLS12_381g2=14850 + 485 per 32 bytes of B", + "Doc": "for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + ... + BnAn", + "DocExtra": "A is a list of concatenated points, encoded and checked as described in `ec_add`. B is a list of concatenated scalars which, unlike ec_scalar_mul, must all be exactly 32 bytes long.\nThe name `ec_multi_scalar_mul` was chosen to reflect common usage, but a more consistent name would be `ec_multi_scalar_mul`. AVM values are limited to 4096 bytes, so `ec_multi_scalar_mul` is limited by the size of the points in the group being operated upon.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "G", + "Reference": "EC" + } + ], + "IntroducedVersion": 10, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 228, + "Name": "ec_subgroup_check", + "Args": [ + "[]byte" + ], + "Returns": [ + "bool" + ], + "Size": 2, + "DocCost": "BN254g1=20; BN254g2=3100; BLS12_381g1=1850; BLS12_381g2=2340", + "Doc": "1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "G", + "Reference": "EC" + } + ], + "IntroducedVersion": 10, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 229, + "Name": "ec_map_to", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], + "Size": 2, + "DocCost": "BN254g1=630; BN254g2=3300; BLS12_381g1=1950; BLS12_381g2=8150", + "Doc": "maps field element A to group G", + "DocExtra": "BN254 points are mapped by the SVDW map. BLS12-381 points are mapped by the SSWU map.\nG1 element inputs are base field elements and G2 element inputs are quadratic field elements, with nearly the same encoding rules (for field elements) as defined in `ec_add`. There is one difference of encoding rule: G1 element inputs do not need to be 0-padded if they fit in less than 32 bytes for BN254 and less than 48 bytes for BLS12-381. (As usual, the empty byte array represents 0.) G2 elements inputs need to be always have the required size.", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "G", + "Reference": "EC" + } + ], + "IntroducedVersion": 10, + "Groups": [ + "Cryptography" + ] + }, + { + "Opcode": 230, + "Name": "mimc", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], + "Size": 2, + "DocCost": "BN254Mp110=10 + 550 per 32 bytes of A; BLS12_381Mp111=10 + 550 per 32 bytes of A", + "Doc": "MiMC hash of scalars A, using curve and parameters specified by configuration C", + "DocExtra": "A is a list of concatenated 32 byte big-endian unsigned integer scalars. Fail if A's length is not a multiple of 32 or any element exceeds the curve modulus.\n\nThe MiMC hash function has known collisions since any input which is a multiple of the elliptic curve modulus will hash to the same value. MiMC is thus not a general purpose hash function, but meant to be used in zero knowledge applications to match a zk-circuit implementation.", + "ImmediateNote": [ + { + "Comment": "configuration index", + "Encoding": "uint8", + "Name": "C", + "Reference": "Mimc Configurations" + } + ], + "IntroducedVersion": 11, + "Groups": [ + "Cryptography" + ] + } + ] +} diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 1a86c280e8..ed46327e9b 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -112,7 +112,7 @@ }, { "name": "variable.parameter.teal", - "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|PayoutsEnabled|PayoutsGoOnlineFee|PayoutsPercent|PayoutsMinBalance|PayoutsMaxBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|AcctIncentiveEligible|AcctLastProposed|AcctLastHeartbeat|VoterBalance|VoterIncentiveEligible|VrfAlgorand|BlkSeed|BlkTimestamp|BlkProposer|BlkFeesCollected|BlkBonus|BlkBranch|BlkFeeSink|BlkProtocol|BlkTxnCounter|BlkProposerPayout|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2|BN254Mp110|BLS12_381Mp111)\\b" + "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|RejectVersion|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|PayoutsEnabled|PayoutsGoOnlineFee|PayoutsPercent|PayoutsMinBalance|PayoutsMaxBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AppVersion|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|AcctIncentiveEligible|AcctLastProposed|AcctLastHeartbeat|VoterBalance|VoterIncentiveEligible|VrfAlgorand|BlkSeed|BlkTimestamp|BlkProposer|BlkFeesCollected|BlkBonus|BlkBranch|BlkFeeSink|BlkProtocol|BlkTxnCounter|BlkProposerPayout|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2|BN254Mp110|BLS12_381Mp111)\\b" } ] }, From 3b1ac0f591e89266b7548ad70cd6444291697f7d Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:57:10 -0400 Subject: [PATCH 20/25] logic: add LogicSig.LMsig field (#6419) --- cmd/goal/multisig.go | 50 +- config/consensus.go | 6 + daemon/kmd/api/swagger.json | 4 + daemon/kmd/api/v1/handlers.go | 4 +- daemon/kmd/client/wrappers.go | 3 +- daemon/kmd/lib/kmdapi/bundledSpecInject.go | 1013 +++++++++-------- daemon/kmd/lib/kmdapi/requests.go | 1 + daemon/kmd/wallet/driver/ledger.go | 2 +- daemon/kmd/wallet/driver/sqlite.go | 21 +- daemon/kmd/wallet/wallet.go | 2 +- data/transactions/logic/program.go | 11 + data/transactions/logicsig.go | 7 +- data/transactions/msgp_gen.go | 39 +- data/transactions/verify/txn.go | 30 +- data/transactions/verify/txn_test.go | 2 +- libgoal/transactions.go | 4 +- protocol/hash.go | 1 + .../kmd/e2e_kmd_wallet_multisig_test.go | 83 +- test/scripts/e2e_subs/e2e-teal-multisig.sh | 86 ++ test/scripts/e2e_subs/e2e-teal.sh | 22 + .../scripts/e2e_subs/v32/e2e-teal-multisig.sh | 120 ++ 21 files changed, 936 insertions(+), 575 deletions(-) create mode 100755 test/scripts/e2e_subs/e2e-teal-multisig.sh create mode 100755 test/scripts/e2e_subs/v32/e2e-teal-multisig.sh diff --git a/cmd/goal/multisig.go b/cmd/goal/multisig.go index c0e5ad9bbe..402e80ec21 100644 --- a/cmd/goal/multisig.go +++ b/cmd/goal/multisig.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "github.com/algorand/go-algorand/cmd/util/datadir" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" @@ -33,9 +34,10 @@ import ( ) var ( - addr string - msigAddr string - noSig bool + addr string + msigAddr string + noSig bool + useLegacyMsig bool ) func init() { @@ -55,6 +57,7 @@ func init() { signProgramCmd.Flags().StringVarP(&addr, "address", "a", "", "Address of the key to sign with") signProgramCmd.Flags().StringVarP(&msigAddr, "msig-address", "A", "", "Multi-Sig Address that signing address is part of") signProgramCmd.Flags().StringVarP(&outFilename, "lsig-out", "o", "", "File to write partial Lsig to") + signProgramCmd.Flags().BoolVar(&useLegacyMsig, "legacy-msig", false, "Use legacy multisig (if not specified, auto-detect consensus params from algod)") signProgramCmd.MarkFlagRequired("address") mergeSigCmd.Flags().StringVarP(&outFilename, "out", "o", "", "Output file for merged transactions") @@ -202,7 +205,31 @@ var signProgramCmd = &cobra.Command{ } lsig.Logic = program } - if !gotPartial { + + if !cmd.Flags().Changed("legacy-msig") { // if not specified, auto-detect from consensus params + params, err := client.SuggestedParams() + if err == nil { + if cparams, ok := config.Consensus[protocol.ConsensusVersion(params.ConsensusVersion)]; ok { + useLegacyMsig = !cparams.LogicSigLMsig + } + } + } + + // Get or create partial multisig from appropriate field + var partial crypto.MultisigSig + if gotPartial { + if useLegacyMsig { + if !lsig.LMsig.Blank() { + reportErrorf("LogicSig file contains LMsig field, but --legacy-msig=true is set, which uses Msig. Specify --legacy-msig=false to use LMsig, or provide a LogicSig file with Msig field") + } + partial = lsig.Msig + } else { + if !lsig.Msig.Blank() { + reportErrorf("LogicSig file contains Msig field, but --legacy-msig=false is set, which uses LMsig. Specify --legacy-msig=true to use Msig, or provide a LogicSig file with LMsig field") + } + partial = lsig.LMsig + } + } else { if msigAddr == "" { reportErrorf("--msig-address/-A required when partial LogicSig not available") } @@ -210,17 +237,24 @@ var signProgramCmd = &cobra.Command{ if err != nil { reportErrorf(msigLookupError, err) } - msig, err := msigInfoToMsig(multisigInfo) + partial, err = msigInfoToMsig(multisigInfo) if err != nil { reportErrorf(msigParseError, err) } - lsig.Msig = msig } - msig, err := client.MultisigSignProgramWithWallet(wh, pw, program, addr, lsig.Msig) + + msig, err := client.MultisigSignProgramWithWallet(wh, pw, program, addr, partial, useLegacyMsig) if err != nil { reportErrorf(errorSigningTX, err) } - lsig.Msig = msig + + if useLegacyMsig { + lsig.Msig = msig + lsig.LMsig = crypto.MultisigSig{} + } else { + lsig.Msig = crypto.MultisigSig{} + lsig.LMsig = msig + } lsigblob := protocol.Encode(&lsig) err = writeFile(outname, lsigblob, 0600) if err != nil { diff --git a/config/consensus.go b/config/consensus.go index 08fb39df6f..3e5f09ffea 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -237,6 +237,9 @@ type ConsensusParams struct { // sum of estimated op cost must be less than this LogicSigMaxCost uint64 + LogicSigMsig bool + LogicSigLMsig bool + // max decimal precision for assets MaxAssetDecimals uint32 @@ -985,6 +988,7 @@ func initConsensusProtocols() { v18.LogicSigVersion = 1 v18.LogicSigMaxSize = 1000 v18.LogicSigMaxCost = 20000 + v18.LogicSigMsig = true v18.MaxAssetsPerAccount = 1000 v18.SupportTxGroups = true v18.MaxTxGroupSize = 16 @@ -1447,6 +1451,8 @@ func initConsensusProtocols() { vFuture.MaxAppAccess = 16 // Twice as many, though cross products are explicit vFuture.BytesPerBoxReference = 2048 // Count is more important that bytes, loosen up vFuture.EnableInnerClawbackWithoutSenderHolding = true + vFuture.LogicSigMsig = false + vFuture.LogicSigLMsig = true Consensus[protocol.ConsensusFuture] = vFuture diff --git a/daemon/kmd/api/swagger.json b/daemon/kmd/api/swagger.json index 3d1812a6ef..b5894f38f2 100644 --- a/daemon/kmd/api/swagger.json +++ b/daemon/kmd/api/swagger.json @@ -1422,6 +1422,10 @@ "public_key": { "$ref": "#/definitions/PublicKey" }, + "use_legacy_msig": { + "type": "boolean", + "x-go-name": "UseLegacyMsig" + }, "wallet_handle_token": { "type": "string", "x-go-name": "WalletHandleToken" diff --git a/daemon/kmd/api/v1/handlers.go b/daemon/kmd/api/v1/handlers.go index 50bdafdbc4..ac2624110b 100644 --- a/daemon/kmd/api/v1/handlers.go +++ b/daemon/kmd/api/v1/handlers.go @@ -1185,8 +1185,8 @@ func postMultisigProgramSignHandler(ctx reqContext, w http.ResponseWriter, r *ht return } - // Sign the transaction - msig, err := wallet.MultisigSignProgram(req.Program, crypto.Digest(reqAddr), req.PublicKey, req.PartialMsig, []byte(req.WalletPassword)) + // Sign the program + msig, err := wallet.MultisigSignProgram(req.Program, crypto.Digest(reqAddr), req.PublicKey, req.PartialMsig, []byte(req.WalletPassword), req.UseLegacyMsig) if err != nil { errorResponse(w, http.StatusBadRequest, err) return diff --git a/daemon/kmd/client/wrappers.go b/daemon/kmd/client/wrappers.go index 9e9b2fdf48..4e1fb7da8c 100644 --- a/daemon/kmd/client/wrappers.go +++ b/daemon/kmd/client/wrappers.go @@ -166,7 +166,7 @@ func (kcl KMDClient) MultisigSignTransaction(walletHandle, pw []byte, tx []byte, } // MultisigSignProgram wraps kmdapi.APIV1POSTMultisigProgramSignRequest -func (kcl KMDClient) MultisigSignProgram(walletHandle, pw []byte, addr string, data []byte, pk crypto.PublicKey, partial crypto.MultisigSig) (resp kmdapi.APIV1POSTMultisigProgramSignResponse, err error) { +func (kcl KMDClient) MultisigSignProgram(walletHandle, pw []byte, addr string, data []byte, pk crypto.PublicKey, partial crypto.MultisigSig, useLegacyMsig bool) (resp kmdapi.APIV1POSTMultisigProgramSignResponse, err error) { req := kmdapi.APIV1POSTMultisigProgramSignRequest{ WalletHandleToken: string(walletHandle), WalletPassword: string(pw), @@ -174,6 +174,7 @@ func (kcl KMDClient) MultisigSignProgram(walletHandle, pw []byte, addr string, d Address: addr, PublicKey: pk, PartialMsig: partial, + UseLegacyMsig: useLegacyMsig, } err = kcl.DoV1Request(req, &resp) return diff --git a/daemon/kmd/lib/kmdapi/bundledSpecInject.go b/daemon/kmd/lib/kmdapi/bundledSpecInject.go index d4682e9a63..ed724480f4 100644 --- a/daemon/kmd/lib/kmdapi/bundledSpecInject.go +++ b/daemon/kmd/lib/kmdapi/bundledSpecInject.go @@ -2689,553 +2689,560 @@ func init() { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x50, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x4B, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, - 0x65, 0x74, 0x5F, 0x68, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, - 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, - 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, - 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x3A, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x73, 0x65, 0x5F, + 0x6C, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5F, 0x6D, 0x73, 0x69, 0x67, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x62, 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x55, 0x73, 0x65, 0x4C, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4D, 0x73, + 0x69, 0x67, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x68, + 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, + 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, + 0x74, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, + 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, + 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x50, 0x72, 0x6F, 0x67, 0x72, + 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, + 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, + 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, + 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, + 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x50, 0x72, + 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, + 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, + 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x60, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, + 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, + 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, - 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x50, 0x61, 0x73, - 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, - 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x50, - 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, - 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, - 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, - 0x67, 0x6E, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, - 0x50, 0x4F, 0x53, 0x54, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, - 0x2F, 0x76, 0x31, 0x2F, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x2F, 0x73, 0x69, 0x67, 0x6E, - 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, - 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, - 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, - 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x68, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, - 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, - 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, - 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x50, - 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, - 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, - 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, - 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, - 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x54, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, - 0x53, 0x54, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, - 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x60, 0x50, 0x4F, 0x53, - 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, - 0x6E, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, - 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, - 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, - 0x2F, 0x50, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x4B, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34, - 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, 0x20, 0x6D, 0x73, 0x67, - 0x70, 0x61, 0x63, 0x6B, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, - 0x20, 0x61, 0x20, 0x60, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x60, - 0x20, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x5C, 0x6E, 0x4E, 0x6F, 0x74, 0x65, 0x3A, 0x20, 0x53, - 0x44, 0x4B, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x67, 0x6F, 0x61, 0x6C, 0x20, 0x75, 0x73, 0x75, 0x61, - 0x6C, 0x6C, 0x79, 0x20, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20, 0x60, 0x53, 0x69, - 0x67, 0x6E, 0x65, 0x64, 0x54, 0x78, 0x6E, 0x60, 0x20, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x73, - 0x5C, 0x6E, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x73, 0x65, 0x2C, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6C, 0x64, 0x20, 0x60, 0x74, 0x78, 0x6E, 0x60, 0x20, - 0x2F, 0x20, 0x60, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x60, 0x20, - 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, - 0x64, 0x20, 0x60, 0x53, 0x69, 0x67, 0x6E, 0x65, 0x64, 0x54, 0x78, 0x6E, 0x60, 0x20, 0x6F, 0x62, - 0x6A, 0x65, 0x63, 0x74, 0x20, 0x6E, 0x65, 0x65, 0x64, 0x73, 0x20, 0x74, 0x6F, 0x20, 0x62, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, - 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, - 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, - 0x68, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x3A, 0x20, 0x7B, + 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, + 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x72, 0x6F, + 0x67, 0x72, 0x61, 0x6D, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, + 0x5F, 0x68, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, + 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, + 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, + 0x6C, 0x65, 0x74, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, 0x6C, - 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, - 0x65, 0x74, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6F, - 0x72, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, - 0x50, 0x4F, 0x53, 0x54, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, - 0x69, 0x67, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, - 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, - 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, - 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x20, 0x53, 0x69, 0x67, - 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x6F, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x2E, 0x20, 0x49, 0x74, 0x20, 0x70, 0x72, 0x6F, 0x76, 0x65, 0x73, 0x20, 0x74, - 0x68, 0x61, 0x74, 0x20, 0x61, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x77, 0x61, - 0x73, 0x5C, 0x6E, 0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, - 0x20, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x6F, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, - 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, - 0x73, 0x2F, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, - 0x79, 0x70, 0x65, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6E, 0x20, 0x74, - 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x65, 0x64, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, - 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x70, 0x72, - 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x69, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x66, 0x6F, 0x72, - 0x20, 0x60, 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x60, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, - 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, - 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, - 0x20, 0x74, 0x6F, 0x20, 0x60, 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, - 0x6E, 0x73, 0x60, 0x5C, 0x6E, 0x66, 0x72, 0x69, 0x65, 0x6E, 0x64, 0x6C, 0x79, 0x3A, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, - 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, - 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, - 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x61, - 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, - 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x71, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6F, 0x72, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, + 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, + 0x31, 0x50, 0x4F, 0x53, 0x54, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, + 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, + 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, + 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x54, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, - 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x69, 0x6E, 0x66, 0x6F, 0x60, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x68, - 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x31, 0x2F, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x73, 0x69, + 0x67, 0x6E, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x75, 0x62, + 0x6C, 0x69, 0x63, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, + 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x50, 0x75, 0x62, + 0x6C, 0x69, 0x63, 0x4B, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6E, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34, 0x20, 0x65, 0x6E, 0x63, + 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, 0x20, 0x6D, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6B, + 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x60, + 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x60, 0x20, 0x6F, 0x62, 0x6A, + 0x65, 0x63, 0x74, 0x5C, 0x6E, 0x4E, 0x6F, 0x74, 0x65, 0x3A, 0x20, 0x53, 0x44, 0x4B, 0x20, 0x61, + 0x6E, 0x64, 0x20, 0x67, 0x6F, 0x61, 0x6C, 0x20, 0x75, 0x73, 0x75, 0x61, 0x6C, 0x6C, 0x79, 0x20, + 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20, 0x60, 0x53, 0x69, 0x67, 0x6E, 0x65, 0x64, + 0x54, 0x78, 0x6E, 0x60, 0x20, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x73, 0x5C, 0x6E, 0x69, 0x6E, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x73, 0x65, 0x2C, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x66, 0x69, 0x65, 0x6C, 0x64, 0x20, 0x60, 0x74, 0x78, 0x6E, 0x60, 0x20, 0x2F, 0x20, 0x60, 0x54, + 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x60, 0x20, 0x6F, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x5C, 0x6E, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x60, 0x53, + 0x69, 0x67, 0x6E, 0x65, 0x64, 0x54, 0x78, 0x6E, 0x60, 0x20, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, + 0x20, 0x6E, 0x65, 0x65, 0x64, 0x73, 0x20, 0x74, 0x6F, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, + 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, - 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, - 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x66, - 0x6F, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, - 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, - 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x50, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, - 0x22, 0x75, 0x69, 0x6E, 0x74, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, - 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, - 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, - 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6F, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x32, 0x35, - 0x35, 0x31, 0x39, 0x50, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x4B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, - 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, - 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x38, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x68, 0x61, 0x6E, 0x64, + 0x6C, 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, 0x6B, + 0x65, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, + 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, + 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, + 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, 0x6E, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, + 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, + 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, + 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x20, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6F, 0x67, 0x72, + 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2E, + 0x20, 0x49, 0x74, 0x20, 0x70, 0x72, 0x6F, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, + 0x61, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x77, 0x61, 0x73, 0x5C, 0x6E, 0x70, + 0x72, 0x6F, 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x68, 0x6F, 0x6C, + 0x64, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6F, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2E, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, + 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x65, 0x64, + 0x32, 0x35, 0x35, 0x31, 0x39, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x78, 0x54, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x78, 0x54, + 0x79, 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, + 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6E, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6C, 0x65, 0x64, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, + 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, + 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, + 0x6F, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, + 0x6E, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x60, 0x47, 0x45, + 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x60, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, + 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, - 0x61, 0x73, 0x73, 0x69, 0x63, 0x61, 0x6C, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x20, 0x2A, 0x2F, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, - 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, - 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x38, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, + 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, + 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, + 0x60, 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x60, 0x5C, + 0x6E, 0x66, 0x72, 0x69, 0x65, 0x6E, 0x64, 0x6C, 0x79, 0x3A, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, + 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, + 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, + 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, + 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, + 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, + 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, + 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, + 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, + 0x66, 0x6F, 0x72, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, + 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x69, 0x6E, 0x66, 0x6F, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, + 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x5F, 0x68, 0x61, 0x6E, 0x64, 0x6C, + 0x65, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, + 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, + 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, + 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x22, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x73, 0x22, 0x3A, 0x20, - 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x61, 0x6C, - 0x6C, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, - 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, - 0x65, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, - 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x44, 0x65, 0x6C, 0x65, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x44, 0x45, 0x4C, - 0x45, 0x54, 0x45, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x60, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, - 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, - 0x41, 0x50, 0x49, 0x56, 0x31, 0x44, 0x45, 0x4C, 0x45, 0x54, 0x45, 0x4B, 0x65, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x44, 0x65, 0x6C, 0x65, - 0x74, 0x65, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, - 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, - 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x64, 0x65, 0x6C, 0x65, 0x74, - 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, - 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, - 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x44, 0x45, 0x4C, 0x45, 0x54, 0x45, - 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x4B, 0x65, 0x79, 0x52, + 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, + 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x6C, 0x69, 0x62, 0x2F, 0x6B, 0x6D, 0x64, + 0x61, 0x70, 0x69, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4B, + 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, + 0x74, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, + 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, + 0x61, 0x6E, 0x64, 0x2F, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x50, + 0x75, 0x62, 0x6C, 0x69, 0x63, 0x4B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, + 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, + 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, + 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, + 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, + 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6F, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, + 0x32, 0x35, 0x35, 0x31, 0x39, 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x61, 0x73, 0x73, 0x69, + 0x63, 0x61, 0x6C, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x20, 0x2A, + 0x2F, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, + 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, + 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, + 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, + 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6F, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, + 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, - 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x2F, 0x65, 0x78, 0x70, 0x6F, - 0x72, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, - 0x4B, 0x65, 0x79, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x4D, 0x61, 0x73, - 0x74, 0x65, 0x72, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, - 0x61, 0x73, 0x74, 0x65, 0x72, 0x2D, 0x6B, 0x65, 0x79, 0x2F, 0x65, 0x78, 0x70, 0x6F, 0x72, 0x74, - 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, - 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, - 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x61, - 0x73, 0x74, 0x65, 0x72, 0x4B, 0x65, 0x79, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6F, 0x72, - 0x74, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x60, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, + 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x44, 0x65, 0x6C, 0x65, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, - 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, - 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x65, 0x78, 0x70, 0x6F, 0x72, - 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, - 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, - 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, + 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x44, 0x45, 0x4C, 0x45, 0x54, 0x45, 0x20, + 0x2F, 0x76, 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, + 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, + 0x31, 0x44, 0x45, 0x4C, 0x45, 0x54, 0x45, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, + 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x44, 0x65, 0x6C, 0x65, 0x74, 0x65, 0x4D, 0x75, + 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6B, - 0x65, 0x79, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, - 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x49, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, - 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, - 0x76, 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x2F, 0x69, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, - 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, - 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4B, 0x65, 0x79, 0x49, 0x6D, - 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x49, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, - 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, - 0x67, 0x2F, 0x69, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, - 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, - 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x49, 0x6D, - 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, + 0x65, 0x20, 0x74, 0x6F, 0x20, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, + 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x64, 0x65, 0x6C, 0x65, 0x74, 0x65, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x44, 0x45, 0x4C, 0x45, 0x54, 0x45, 0x4D, 0x75, 0x6C, 0x74, + 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x49, 0x6E, 0x69, 0x74, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, - 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, - 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, - 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x69, 0x6E, 0x69, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, - 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, - 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, - 0x6E, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x4C, 0x69, 0x73, 0x74, 0x4B, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, - 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, - 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x2F, 0x6C, 0x69, 0x73, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, - 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, - 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4B, 0x65, 0x79, 0x4C, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4C, 0x69, 0x73, - 0x74, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, - 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, - 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x6C, 0x69, 0x73, 0x74, 0x60, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, - 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, - 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, - 0x74, 0x69, 0x73, 0x69, 0x67, 0x4C, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4C, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6C, 0x6C, 0x65, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, - 0x20, 0x60, 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, - 0x73, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, - 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x47, 0x45, 0x54, 0x57, 0x61, - 0x6C, 0x6C, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x52, 0x65, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, - 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, + 0x20, 0x22, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, - 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x72, 0x65, 0x6C, 0x65, 0x61, - 0x73, 0x65, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, - 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x52, 0x65, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x65, 0x6E, 0x61, 0x6D, - 0x65, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, - 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, - 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x72, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x60, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, - 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, - 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, - 0x74, 0x52, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x65, 0x6E, 0x65, 0x77, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, - 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, - 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, - 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, - 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x72, 0x65, 0x6E, 0x65, 0x77, 0x60, 0x22, 0x2C, + 0x2F, 0x76, 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x2F, 0x65, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, + 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4B, 0x65, 0x79, 0x45, + 0x78, 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x4D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4B, + 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, + 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x61, 0x73, 0x74, 0x65, + 0x72, 0x2D, 0x6B, 0x65, 0x79, 0x2F, 0x65, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x4B, 0x65, 0x79, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x4D, 0x75, 0x6C, + 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, + 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, + 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x65, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, - 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, - 0x74, 0x52, 0x65, 0x6E, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, - 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, - 0x67, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, + 0x73, 0x69, 0x67, 0x45, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4B, + 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, + 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6B, 0x65, 0x79, 0x60, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, + 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4B, 0x65, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6D, 0x70, + 0x6F, 0x72, 0x74, 0x4B, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6B, + 0x65, 0x79, 0x2F, 0x69, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, + 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, + 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4B, 0x65, 0x79, 0x49, 0x6D, 0x70, 0x6F, 0x72, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6D, + 0x70, 0x6F, 0x72, 0x74, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, + 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, + 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x69, 0x6D, + 0x70, 0x6F, 0x72, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, + 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, + 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, + 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x49, 0x6D, 0x70, 0x6F, 0x72, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6E, + 0x69, 0x74, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x54, 0x6F, + 0x6B, 0x65, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, + 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, + 0x65, 0x74, 0x2F, 0x69, 0x6E, 0x69, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, + 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, + 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x69, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4C, 0x69, 0x73, + 0x74, 0x4B, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, + 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6B, 0x65, + 0x79, 0x2F, 0x6C, 0x69, 0x73, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, - 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x54, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, + 0x50, 0x4F, 0x53, 0x54, 0x4B, 0x65, 0x79, 0x4C, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x50, 0x72, 0x6F, - 0x67, 0x72, 0x61, 0x6D, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4C, 0x69, 0x73, 0x74, 0x4D, 0x75, 0x6C, + 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, + 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, + 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x6C, 0x69, 0x73, 0x74, 0x60, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, + 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, + 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, + 0x67, 0x4C, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x4C, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x47, 0x45, + 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x73, 0x60, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, + 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, + 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x47, 0x45, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, + 0x65, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, + 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, + 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, + 0x77, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x72, 0x65, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x60, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, + 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, + 0x65, 0x74, 0x52, 0x65, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x57, 0x61, 0x6C, + 0x6C, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, + 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, + 0x65, 0x74, 0x2F, 0x72, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, + 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, + 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x52, 0x65, 0x6E, + 0x61, 0x6D, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x52, 0x65, 0x6E, 0x65, 0x77, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x48, 0x61, 0x6E, 0x64, + 0x6C, 0x65, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, + 0x73, 0x65, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, + 0x6C, 0x65, 0x74, 0x2F, 0x72, 0x65, 0x6E, 0x65, 0x77, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, + 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, + 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x52, 0x65, 0x6E, + 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x53, 0x69, 0x67, 0x6E, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x73, 0x69, - 0x67, 0x6E, 0x64, 0x61, 0x74, 0x61, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, - 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, - 0x50, 0x4F, 0x53, 0x54, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x50, 0x72, 0x6F, 0x67, - 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, - 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, - 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, - 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, - 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x54, 0x72, 0x61, - 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, + 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, + 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, - 0x2F, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x73, 0x69, 0x67, - 0x6E, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, - 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x54, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, - 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, + 0x2F, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x64, 0x61, + 0x74, 0x61, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, + 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, + 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, + 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x73, 0x69, 0x67, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x53, + 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x53, 0x69, 0x67, 0x6E, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x52, 0x65, 0x73, 0x70, + 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, + 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, + 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x60, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, + 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x50, 0x72, 0x6F, 0x67, + 0x72, 0x61, 0x6D, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x69, 0x67, 0x6E, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, - 0x74, 0x6F, 0x20, 0x60, 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, - 0x73, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, - 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, + 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x74, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x73, 0x69, 0x67, 0x6E, 0x60, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, + 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, + 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x54, 0x72, 0x61, 0x6E, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x69, 0x67, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, + 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, + 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x60, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, + 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, + 0x73, 0x2F, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, + 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, + 0x66, 0x6F, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, + 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, 0x61, 0x6C, 0x6C, 0x65, + 0x74, 0x2F, 0x69, 0x6E, 0x66, 0x6F, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, + 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x50, 0x49, 0x56, 0x31, + 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x61, 0x6C, 0x6C, - 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x20, 0x74, 0x6F, 0x20, 0x60, 0x50, 0x4F, 0x53, 0x54, 0x20, 0x2F, 0x76, 0x31, 0x2F, 0x77, - 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x2F, 0x69, 0x6E, 0x66, 0x6F, 0x60, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, - 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, - 0x50, 0x49, 0x56, 0x31, 0x50, 0x4F, 0x53, 0x54, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x49, 0x6E, - 0x66, 0x6F, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x22, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, - 0x70, 0x69, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x2E, 0x20, 0x54, 0x68, 0x69, - 0x73, 0x20, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x63, 0x61, 0x6E, 0x20, 0x62, 0x65, 0x20, 0x66, - 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x60, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x64, 0x61, - 0x74, 0x61, 0x2F, 0x64, 0x69, 0x72, 0x2F, 0x6B, 0x6D, 0x64, 0x2E, 0x74, 0x6F, 0x6B, 0x65, 0x6E, - 0x60, 0x2E, 0x20, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x76, 0x61, 0x6C, 0x75, 0x65, - 0x3A, 0x20, 0x27, 0x33, 0x33, 0x30, 0x62, 0x32, 0x65, 0x34, 0x66, 0x63, 0x39, 0x62, 0x32, 0x30, - 0x66, 0x34, 0x66, 0x38, 0x39, 0x38, 0x31, 0x32, 0x63, 0x66, 0x38, 0x37, 0x66, 0x31, 0x64, 0x61, - 0x62, 0x65, 0x62, 0x37, 0x31, 0x36, 0x64, 0x32, 0x33, 0x65, 0x33, 0x66, 0x31, 0x31, 0x61, 0x65, - 0x63, 0x39, 0x37, 0x61, 0x36, 0x31, 0x66, 0x66, 0x35, 0x66, 0x37, 0x35, 0x30, 0x35, 0x36, 0x33, - 0x62, 0x37, 0x38, 0x27, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x70, 0x69, 0x4B, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x58, 0x2D, - 0x4B, 0x4D, 0x44, 0x2D, 0x41, 0x50, 0x49, 0x2D, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x65, - 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x33, 0x33, 0x30, 0x62, 0x32, 0x65, - 0x34, 0x66, 0x63, 0x39, 0x62, 0x32, 0x30, 0x66, 0x34, 0x66, 0x38, 0x39, 0x38, 0x31, 0x32, 0x63, - 0x66, 0x38, 0x37, 0x66, 0x31, 0x64, 0x61, 0x62, 0x65, 0x62, 0x37, 0x31, 0x36, 0x64, 0x32, 0x33, - 0x65, 0x33, 0x66, 0x31, 0x31, 0x61, 0x65, 0x63, 0x39, 0x37, 0x61, 0x36, 0x31, 0x66, 0x66, 0x35, - 0x66, 0x37, 0x35, 0x30, 0x35, 0x36, 0x33, 0x62, 0x37, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, - 0x74, 0x79, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x69, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x5B, 0x5D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x5D, 0x0A, 0x7D, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x73, 0x65, + 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, + 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x69, 0x5F, 0x6B, + 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x70, 0x61, + 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x2E, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x76, 0x61, + 0x6C, 0x75, 0x65, 0x20, 0x63, 0x61, 0x6E, 0x20, 0x62, 0x65, 0x20, 0x66, 0x6F, 0x75, 0x6E, 0x64, + 0x20, 0x69, 0x6E, 0x20, 0x60, 0x2F, 0x6B, 0x6D, 0x64, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x64, + 0x69, 0x72, 0x2F, 0x6B, 0x6D, 0x64, 0x2E, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x60, 0x2E, 0x20, 0x45, + 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x3A, 0x20, 0x27, 0x33, + 0x33, 0x30, 0x62, 0x32, 0x65, 0x34, 0x66, 0x63, 0x39, 0x62, 0x32, 0x30, 0x66, 0x34, 0x66, 0x38, + 0x39, 0x38, 0x31, 0x32, 0x63, 0x66, 0x38, 0x37, 0x66, 0x31, 0x64, 0x61, 0x62, 0x65, 0x62, 0x37, + 0x31, 0x36, 0x64, 0x32, 0x33, 0x65, 0x33, 0x66, 0x31, 0x31, 0x61, 0x65, 0x63, 0x39, 0x37, 0x61, + 0x36, 0x31, 0x66, 0x66, 0x35, 0x66, 0x37, 0x35, 0x30, 0x35, 0x36, 0x33, 0x62, 0x37, 0x38, 0x27, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x61, 0x70, 0x69, 0x4B, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x58, 0x2D, 0x4B, 0x4D, 0x44, 0x2D, + 0x41, 0x50, 0x49, 0x2D, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x69, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x65, 0x78, 0x61, 0x6D, 0x70, + 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x33, 0x33, 0x30, 0x62, 0x32, 0x65, 0x34, 0x66, 0x63, 0x39, + 0x62, 0x32, 0x30, 0x66, 0x34, 0x66, 0x38, 0x39, 0x38, 0x31, 0x32, 0x63, 0x66, 0x38, 0x37, 0x66, + 0x31, 0x64, 0x61, 0x62, 0x65, 0x62, 0x37, 0x31, 0x36, 0x64, 0x32, 0x33, 0x65, 0x33, 0x66, 0x31, + 0x31, 0x61, 0x65, 0x63, 0x39, 0x37, 0x61, 0x36, 0x31, 0x66, 0x66, 0x35, 0x66, 0x37, 0x35, 0x30, + 0x35, 0x36, 0x33, 0x62, 0x37, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x22, 0x3A, + 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x61, 0x70, 0x69, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x5B, 0x5D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x5D, 0x0A, 0x7D, }) } diff --git a/daemon/kmd/lib/kmdapi/requests.go b/daemon/kmd/lib/kmdapi/requests.go index 3a51c2b583..b355291eed 100644 --- a/daemon/kmd/lib/kmdapi/requests.go +++ b/daemon/kmd/lib/kmdapi/requests.go @@ -261,4 +261,5 @@ type APIV1POSTMultisigProgramSignRequest struct { PublicKey crypto.PublicKey `json:"public_key"` PartialMsig crypto.MultisigSig `json:"partial_multisig"` WalletPassword string `json:"wallet_password"` + UseLegacyMsig bool `json:"use_legacy_msig"` } diff --git a/daemon/kmd/wallet/driver/ledger.go b/daemon/kmd/wallet/driver/ledger.go index 8bca370920..0bcbceabba 100644 --- a/daemon/kmd/wallet/driver/ledger.go +++ b/daemon/kmd/wallet/driver/ledger.go @@ -398,7 +398,7 @@ func (lw *LedgerWallet) MultisigSignTransaction(tx transactions.Transaction, pk } // MultisigSignProgram implements the Wallet interface. -func (lw *LedgerWallet) MultisigSignProgram(data []byte, src crypto.Digest, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte) (crypto.MultisigSig, error) { +func (lw *LedgerWallet) MultisigSignProgram(data []byte, src crypto.Digest, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte, useLegacyMsig bool) (crypto.MultisigSig, error) { isValidKey := false for i := 0; i < len(partial.Subsigs); i++ { subsig := &partial.Subsigs[i] diff --git a/daemon/kmd/wallet/driver/sqlite.go b/daemon/kmd/wallet/driver/sqlite.go index 8dd29d4abd..5269e72559 100644 --- a/daemon/kmd/wallet/driver/sqlite.go +++ b/daemon/kmd/wallet/driver/sqlite.go @@ -1263,7 +1263,7 @@ func (sw *SQLiteWallet) MultisigSignTransaction(tx transactions.Transaction, pk // MultisigSignProgram starts a multisig signature or adds a signature to a // partially signed multisig transaction signature of the passed transaction // using the key -func (sw *SQLiteWallet) MultisigSignProgram(data []byte, src crypto.Digest, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte) (sig crypto.MultisigSig, err error) { +func (sw *SQLiteWallet) MultisigSignProgram(data []byte, src crypto.Digest, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte, useLegacyMsig bool) (sig crypto.MultisigSig, err error) { // Check the password err = sw.CheckPassword(pw) if err != nil { @@ -1296,10 +1296,13 @@ func (sw *SQLiteWallet) MultisigSignProgram(data []byte, src crypto.Digest, pk c return } - // Sign the transaction + // Sign the program from := src - progb := logic.Program(data) - sig, err = crypto.MultisigSign(&progb, from, version, threshold, pks, *secrets) + if useLegacyMsig { + sig, err = crypto.MultisigSign(logic.Program(data), from, version, threshold, pks, *secrets) + } else { + sig, err = crypto.MultisigSign(logic.MultisigProgram{Addr: from, Program: data}, from, version, threshold, pks, *secrets) + } return } @@ -1340,10 +1343,14 @@ func (sw *SQLiteWallet) MultisigSignProgram(data []byte, src crypto.Digest, pk c return } - // Sign the transaction, and merge the multisig into the partial + // Sign the program and merge the multisig into the partial version, threshold, pks := partial.Preimage() - progb := logic.Program(data) - msig2, err := crypto.MultisigSign(&progb, addr, version, threshold, pks, *secrets) + var msig2 crypto.MultisigSig + if useLegacyMsig { + msig2, err = crypto.MultisigSign(logic.Program(data), addr, version, threshold, pks, *secrets) + } else { + msig2, err = crypto.MultisigSign(logic.MultisigProgram{Addr: addr, Program: data}, addr, version, threshold, pks, *secrets) + } if err != nil { return } diff --git a/daemon/kmd/wallet/wallet.go b/daemon/kmd/wallet/wallet.go index 4822054a7a..9c4921e6cd 100644 --- a/daemon/kmd/wallet/wallet.go +++ b/daemon/kmd/wallet/wallet.go @@ -56,7 +56,7 @@ type Wallet interface { MultisigSignTransaction(tx transactions.Transaction, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte, signer crypto.Digest) (crypto.MultisigSig, error) SignProgram(program []byte, src crypto.Digest, pw []byte) ([]byte, error) - MultisigSignProgram(program []byte, src crypto.Digest, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte) (crypto.MultisigSig, error) + MultisigSignProgram(program []byte, src crypto.Digest, pk crypto.PublicKey, partial crypto.MultisigSig, pw []byte, useLegacyMsig bool) (crypto.MultisigSig, error) } // Metadata represents high-level information about a wallet, like its name, id diff --git a/data/transactions/logic/program.go b/data/transactions/logic/program.go index b96dd1af24..5747ad9a5c 100644 --- a/data/transactions/logic/program.go +++ b/data/transactions/logic/program.go @@ -29,6 +29,17 @@ func (lsl Program) ToBeHashed() (protocol.HashID, []byte) { return protocol.Program, []byte(lsl) } +// MultisigProgram is a wrapper for signing programs with multisig addresses. +type MultisigProgram struct { + Addr crypto.Digest + Program []byte +} + +// ToBeHashed implements crypto.Hashable for MultisigProgram +func (mp MultisigProgram) ToBeHashed() (protocol.HashID, []byte) { + return protocol.MultisigProgram, append(mp.Addr[:], mp.Program...) +} + // HashProgram takes program bytes and returns the Digest // This Digest can be used as an Address for a logic controlled account. func HashProgram(program []byte) crypto.Digest { diff --git a/data/transactions/logicsig.go b/data/transactions/logicsig.go index a3883e55ba..ad861f0a9b 100644 --- a/data/transactions/logicsig.go +++ b/data/transactions/logicsig.go @@ -40,8 +40,9 @@ type LogicSig struct { // Logic signed by Sig or Msig, OR hashed to be the Address of an account. Logic []byte `codec:"l,allocbound=bounds.MaxLogicSigMaxSize"` - Sig crypto.Signature `codec:"sig"` - Msig crypto.MultisigSig `codec:"msig"` + Sig crypto.Signature `codec:"sig"` + Msig crypto.MultisigSig `codec:"msig"` + LMsig crypto.MultisigSig `codec:"lmsig"` // Args are not signed, but checked by Logic Args [][]byte `codec:"arg,allocbound=EvalMaxArgs,allocbound=MaxLogicSigArgSize,maxtotalbytes=bounds.MaxLogicSigMaxSize"` @@ -69,7 +70,7 @@ func (lsig *LogicSig) Len() int { // different behaviors within the evaluation of a LogicSig, // due to differences in msgpack encoding behavior. func (lsig *LogicSig) Equal(b *LogicSig) bool { - sigs := lsig.Sig == b.Sig && lsig.Msig.Equal(b.Msig) + sigs := lsig.Sig == b.Sig && lsig.Msig.Equal(b.Msig) && lsig.LMsig.Equal(b.LMsig) if !sigs { return false } diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index edb16ea5c1..a0d916fa10 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -3810,8 +3810,8 @@ func LocalsRefMaxSize() (s int) { func (z *LogicSig) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0002Len := uint32(4) - var zb0002Mask uint8 /* 5 bits */ + zb0002Len := uint32(5) + var zb0002Mask uint8 /* 6 bits */ if len((*z).Args) == 0 { zb0002Len-- zb0002Mask |= 0x2 @@ -3820,14 +3820,18 @@ func (z *LogicSig) MarshalMsg(b []byte) (o []byte) { zb0002Len-- zb0002Mask |= 0x4 } - if (*z).Msig.MsgIsZero() { + if (*z).LMsig.MsgIsZero() { zb0002Len-- zb0002Mask |= 0x8 } - if (*z).Sig.MsgIsZero() { + if (*z).Msig.MsgIsZero() { zb0002Len-- zb0002Mask |= 0x10 } + if (*z).Sig.MsgIsZero() { + zb0002Len-- + zb0002Mask |= 0x20 + } // variable map header, size zb0002Len o = append(o, 0x80|uint8(zb0002Len)) if zb0002Len != 0 { @@ -3849,11 +3853,16 @@ func (z *LogicSig) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendBytes(o, (*z).Logic) } if (zb0002Mask & 0x8) == 0 { // if not empty + // string "lmsig" + o = append(o, 0xa5, 0x6c, 0x6d, 0x73, 0x69, 0x67) + o = (*z).LMsig.MarshalMsg(o) + } + if (zb0002Mask & 0x10) == 0 { // if not empty // string "msig" o = append(o, 0xa4, 0x6d, 0x73, 0x69, 0x67) o = (*z).Msig.MarshalMsg(o) } - if (zb0002Mask & 0x10) == 0 { // if not empty + if (zb0002Mask & 0x20) == 0 { // if not empty // string "sig" o = append(o, 0xa3, 0x73, 0x69, 0x67) o = (*z).Sig.MarshalMsg(o) @@ -3919,6 +3928,14 @@ func (z *LogicSig) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o return } } + if zb0002 > 0 { + zb0002-- + bts, err = (*z).LMsig.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LMsig") + return + } + } if zb0002 > 0 { zb0002-- var zb0005 int @@ -4009,6 +4026,12 @@ func (z *LogicSig) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o err = msgp.WrapError(err, "Msig") return } + case "lmsig": + bts, err = (*z).LMsig.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "LMsig") + return + } case "arg": var zb0009 int var zb0010 bool @@ -4069,7 +4092,7 @@ func (_ *LogicSig) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *LogicSig) Msgsize() (s int) { - s = 1 + 2 + msgp.BytesPrefixSize + len((*z).Logic) + 4 + (*z).Sig.Msgsize() + 5 + (*z).Msig.Msgsize() + 4 + msgp.ArrayHeaderSize + s = 1 + 2 + msgp.BytesPrefixSize + len((*z).Logic) + 4 + (*z).Sig.Msgsize() + 5 + (*z).Msig.Msgsize() + 6 + (*z).LMsig.Msgsize() + 4 + msgp.ArrayHeaderSize for zb0001 := range (*z).Args { s += msgp.BytesPrefixSize + len((*z).Args[zb0001]) } @@ -4078,12 +4101,12 @@ func (z *LogicSig) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *LogicSig) MsgIsZero() bool { - return (len((*z).Logic) == 0) && ((*z).Sig.MsgIsZero()) && ((*z).Msig.MsgIsZero()) && (len((*z).Args) == 0) + return (len((*z).Logic) == 0) && ((*z).Sig.MsgIsZero()) && ((*z).Msig.MsgIsZero()) && ((*z).LMsig.MsgIsZero()) && (len((*z).Args) == 0) } // MaxSize returns a maximum valid message size for this message type func LogicSigMaxSize() (s int) { - s = 1 + 2 + msgp.BytesPrefixSize + bounds.MaxLogicSigMaxSize + 4 + crypto.SignatureMaxSize() + 5 + crypto.MultisigSigMaxSize() + 4 + s = 1 + 2 + msgp.BytesPrefixSize + bounds.MaxLogicSigMaxSize + 4 + crypto.SignatureMaxSize() + 5 + crypto.MultisigSigMaxSize() + 6 + crypto.MultisigSigMaxSize() + 4 // Calculating size of slice: z.Args s += msgp.ArrayHeaderSize + bounds.MaxLogicSigMaxSize return diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index ec4c112810..91ee51d6fc 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -395,6 +395,7 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier } hasMsig := false + hasLMsig := false numSigs := 0 if !lsig.Sig.Blank() { numSigs++ @@ -403,6 +404,10 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier hasMsig = true numSigs++ } + if !lsig.LMsig.Blank() { + hasLMsig = true + numSigs++ + } if numSigs == 0 { // if the txn.Authorizer() == hash(Logic) then this is a (potentially) valid operation on a contract-only account program := logic.Program(lsig.Logic) @@ -413,18 +418,33 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier return errors.New("LogicNot signed and not a Logic-only account") } if numSigs > 1 { - return errors.New("LogicSig should only have one of Sig or Msig but has more than one") + return errors.New("LogicSig should only have one of Sig, Msig, or LMsig but has more than one") } - if !hasMsig { + if !hasMsig && !hasLMsig { program := logic.Program(lsig.Logic) batchVerifier.EnqueueSignature(crypto.PublicKey(txn.Authorizer()), &program, lsig.Sig) } else { - program := logic.Program(lsig.Logic) - if err := crypto.MultisigBatchPrep(&program, crypto.Digest(txn.Authorizer()), lsig.Msig, batchVerifier); err != nil { + var program crypto.Hashable + var msig crypto.MultisigSig + if hasLMsig { + if !groupCtx.consensusParams.LogicSigLMsig { + return errors.New("LogicSig LMsig field not supported in this consensus version") + } + program = logic.MultisigProgram{Addr: crypto.Digest(txn.Authorizer()), Program: lsig.Logic} + msig = crypto.MultisigSig(lsig.LMsig) + } else { + if !groupCtx.consensusParams.LogicSigMsig { + return errors.New("LogicSig Msig field not supported in this consensus version") + } + program = logic.Program(lsig.Logic) + msig = lsig.Msig + } + if err := crypto.MultisigBatchPrep(program, crypto.Digest(txn.Authorizer()), msig, batchVerifier); err != nil { return fmt.Errorf("logic multisig validation failed: %w", err) } - sigs := lsig.Msig.Signatures() + + sigs := msig.Signatures() if sigs <= 4 { msigLsigLessOrEqual4.Inc(nil) } else if sigs <= 10 { diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 4c5b5bc86d..55665cdae6 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -820,7 +820,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= } _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.Error(t, err) - require.Contains(t, err.Error(), "should only have one of Sig or Msig") + require.Contains(t, err.Error(), "only have one of Sig, Msig, or LMsig") } diff --git a/libgoal/transactions.go b/libgoal/transactions.go index db3595396b..38fafabd9c 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -140,7 +140,7 @@ func (c *Client) MultisigSignTransactionWithWalletAndSigner(walletHandle, pw []b } // MultisigSignProgramWithWallet creates a multisig (or adds to an existing partial multisig, if one is provided), signing with the key corresponding to the given address and using the specified wallet -func (c *Client) MultisigSignProgramWithWallet(walletHandle, pw, program []byte, signerAddr string, partial crypto.MultisigSig) (msig crypto.MultisigSig, err error) { +func (c *Client) MultisigSignProgramWithWallet(walletHandle, pw, program []byte, signerAddr string, partial crypto.MultisigSig, useLegacyMsig bool) (msig crypto.MultisigSig, err error) { addr, err := basics.UnmarshalChecksumAddress(signerAddr) if err != nil { return @@ -153,7 +153,7 @@ func (c *Client) MultisigSignProgramWithWallet(walletHandle, pw, program []byte, if err != nil { return } - resp, err := kmd.MultisigSignProgram(walletHandle, pw, basics.Address(msigAddr).String(), program, crypto.PublicKey(addr), partial) + resp, err := kmd.MultisigSignProgram(walletHandle, pw, basics.Address(msigAddr).String(), program, crypto.PublicKey(addr), partial, useLegacyMsig) if err != nil { return } diff --git a/protocol/hash.go b/protocol/hash.go index e652f84c6c..2ac6842f81 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -48,6 +48,7 @@ const ( MerkleArrayNode HashID = "MA" MerkleVectorCommitmentBottomLeaf HashID = "MB" Message HashID = "MX" + MultisigProgram HashID = "MsigProgram" NetIdentityChallenge HashID = "NIC" NetIdentityChallengeResponse HashID = "NIR" NetIdentityVerificationMessage HashID = "NIV" diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go index d68668eee9..b69ffa76b7 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go @@ -407,39 +407,56 @@ func TestMultisigSignProgram(t *testing.T) { program := []byte("blah blah blah, not a real program, just some bytes to sign, kmd does not have a program interpreter to know if the program is legitimate, but it _does_ prefix the program with protocol.Program and we can verify that here below") - // Try to sign - req2 := kmdapi.APIV1POSTMultisigProgramSignRequest{ - WalletHandleToken: walletHandleToken, - Program: program, - Address: basics.Address(msigAddr).String(), - PublicKey: pk1, - PartialMsig: crypto.MultisigSig{}, - WalletPassword: f.WalletPassword, + testMultisigSign := func(t *testing.T, useLegacyMsig bool) { + // Try to sign + req2 := kmdapi.APIV1POSTMultisigProgramSignRequest{ + WalletHandleToken: walletHandleToken, + Program: program, + Address: basics.Address(msigAddr).String(), + PublicKey: pk1, + PartialMsig: crypto.MultisigSig{}, + WalletPassword: f.WalletPassword, + UseLegacyMsig: useLegacyMsig, + } + resp2 := kmdapi.APIV1POSTMultisigProgramSignResponse{} + err = f.Client.DoV1Request(req2, &resp2) + a.NoError(err) + + var msig crypto.MultisigSig + err = protocol.Decode(resp2.Multisig, &msig) + a.NoError(err) + + // Try to add another signature + req3 := kmdapi.APIV1POSTMultisigProgramSignRequest{ + WalletHandleToken: walletHandleToken, + Program: program, + Address: basics.Address(msigAddr).String(), + PublicKey: pk2, + PartialMsig: msig, + WalletPassword: f.WalletPassword, + UseLegacyMsig: useLegacyMsig, + } + resp3 := kmdapi.APIV1POSTMultisigProgramSignResponse{} + err = f.Client.DoV1Request(req3, &resp3) + a.NoError(err) + + err = protocol.Decode(resp3.Multisig, &msig) + a.NoError(err) + + var prog, wrongProg crypto.Hashable + if useLegacyMsig { + prog = logic.Program(program) + wrongProg = logic.MultisigProgram{Addr: crypto.Digest(msigAddr), Program: program} + } else { + prog = logic.MultisigProgram{Addr: crypto.Digest(msigAddr), Program: program} + wrongProg = logic.Program(program) + } + err = crypto.MultisigVerify(prog, crypto.Digest(msigAddr), msig) + a.NoError(err) + err = crypto.MultisigVerify(wrongProg, crypto.Digest(msigAddr), msig) + a.ErrorContains(err, "At least one signature didn't pass verification") } - resp2 := kmdapi.APIV1POSTMultisigProgramSignResponse{} - err = f.Client.DoV1Request(req2, &resp2) - a.NoError(err) - - var msig crypto.MultisigSig - err = protocol.Decode(resp2.Multisig, &msig) - a.NoError(err) - // Try to add another signature - req3 := kmdapi.APIV1POSTMultisigProgramSignRequest{ - WalletHandleToken: walletHandleToken, - Program: program, - Address: basics.Address(msigAddr).String(), - PublicKey: pk2, - PartialMsig: msig, - WalletPassword: f.WalletPassword, - } - resp3 := kmdapi.APIV1POSTMultisigProgramSignResponse{} - err = f.Client.DoV1Request(req3, &resp3) - a.NoError(err) - - err = protocol.Decode(resp3.Multisig, &msig) - a.NoError(err) - - err = crypto.MultisigVerify(logic.Program(program), crypto.Digest(msigAddr), msig) - a.NoError(err) + t.Run("LegacyMsig", func(t *testing.T) { testMultisigSign(t, true) }) + t.Run("NewLMsig", func(t *testing.T) { testMultisigSign(t, false) }) } diff --git a/test/scripts/e2e_subs/e2e-teal-multisig.sh b/test/scripts/e2e_subs/e2e-teal-multisig.sh new file mode 100755 index 0000000000..d5acb06c18 --- /dev/null +++ b/test/scripts/e2e_subs/e2e-teal-multisig.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +date '+e2e_teal_multisig_future start %Y%m%d_%H%M%S' + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +gcmd="goal -w ${WALLET}" + +# Create 2-of-3 multisig +ACCOUNT_A=$(${gcmd} account list|awk '{ print $3 }') +ACCOUNT_B=$(${gcmd} account new|awk '{ print $6 }') +ACCOUNT_C=$(${gcmd} account new|awk '{ print $6 }') +ACCOUNT_MSIG=$(${gcmd} account multisig new -T 2 ${ACCOUNT_A} ${ACCOUNT_B} ${ACCOUNT_C}|awk '{ print $6 }') + +cat >${TEMPDIR}/msig_true.teal<&1 | tee ${TEMPDIR}/legacy_send.out +SEND_RESULT=$? +set -e + +if [ $SEND_RESULT -eq 0 ] || ! grep -q "Msig field not supported" ${TEMPDIR}/legacy_send.out; then + echo "ERROR: Expected failure with 'Msig field not supported' error but got:" + cat ${TEMPDIR}/legacy_send.out + exit 1 +fi +echo "Legacy mode transaction rejected on vFuture" + +# Sign with new mode explicitly set +${gcmd} clerk multisig signprogram --legacy-msig=false -p ${TEMPDIR}/msig_true.teal -a ${ACCOUNT_A} -A ${ACCOUNT_MSIG} -o ${TEMPDIR}/new1.lsig +${gcmd} clerk multisig signprogram --legacy-msig=false -L ${TEMPDIR}/new1.lsig -a ${ACCOUNT_B} -o ${TEMPDIR}/new2.lsig +# Use the new LMsig on vFuture, should succeed +${gcmd} clerk send --amount 100000 --from ${ACCOUNT_MSIG} --to ${ACCOUNT_A} -L ${TEMPDIR}/new2.lsig +if [ $? -ne 0 ]; then + echo "ERROR: New mode transaction failed on future consensus" + exit 1 +fi +echo "New mode transaction succeeded on future consensus" + +# Error cases +set +e +OUTPUT=$(${gcmd} clerk multisig signprogram --legacy-msig=false -L ${TEMPDIR}/legacy1.lsig -a ${ACCOUNT_C} -o ${TEMPDIR}/mixed.lsig 2>&1) +if [ $? -eq 0 ] || ! echo "$OUTPUT" | grep -q "contains Msig field"; then + echo "ERROR: Expected failure with 'contains Msig field' error but got:" + echo "$OUTPUT" + exit 1 +fi +echo "Correctly rejected mixing legacy signature with new mode" + +OUTPUT2=$(${gcmd} clerk multisig signprogram --legacy-msig=true -L ${TEMPDIR}/new1.lsig -a ${ACCOUNT_C} -o ${TEMPDIR}/mixed2.lsig 2>&1) +if [ $? -eq 0 ] || ! echo "$OUTPUT2" | grep -q "contains LMsig field"; then + echo "ERROR: Expected failure with 'contains LMsig field' error but got:" + echo "$OUTPUT2" + exit 1 +fi +echo "Correctly rejected mixing new signature with legacy mode" +set -e + +# Sign and send without specifying mode - should auto-detect and use new mode on vFuture +${gcmd} clerk multisig signprogram -p ${TEMPDIR}/msig_true.teal -a ${ACCOUNT_A} -A ${ACCOUNT_MSIG} -o ${TEMPDIR}/auto1.lsig +${gcmd} clerk multisig signprogram -L ${TEMPDIR}/auto1.lsig -a ${ACCOUNT_B} -o ${TEMPDIR}/auto2.lsig +${gcmd} clerk send --amount 100000 --from ${ACCOUNT_MSIG} --to ${ACCOUNT_A} -L ${TEMPDIR}/auto2.lsig +echo "Auto-detection correctly used new mode on future consensus" + +# Verify auto-detection used new mode (LMsig field) +if ! cat ${TEMPDIR}/auto2.lsig | msgpacktool -d | grep -q '"lmsig"'; then + echo "ERROR: Auto-detection did not use new mode (LMsig field not found)" + exit 1 +fi +echo "Auto-detection used new mode (LMsig field present)" + +date '+e2e_teal_multisig_future done %Y%m%d_%H%M%S' diff --git a/test/scripts/e2e_subs/e2e-teal.sh b/test/scripts/e2e_subs/e2e-teal.sh index 7a5975dc9d..5351320b37 100755 --- a/test/scripts/e2e_subs/e2e-teal.sh +++ b/test/scripts/e2e_subs/e2e-teal.sh @@ -120,6 +120,28 @@ ${gcmd} clerk send --amount 1000000 --from ${ACCOUNT} --to ${ACCOUNTM} ${gcmd} clerk send --amount 200000 --from ${ACCOUNTM} --to ${ACCOUNTC} -L ${TEMPDIR}/mtrue.lsig +# Test new multisig mode (e2e using vFuture) +echo "Testing multisig mode..." +${gcmd} clerk multisig signprogram --legacy-msig=false -p ${TEMPDIR}/true.teal -a ${ACCOUNT} -A ${ACCOUNTM} -o ${TEMPDIR}/mtrue_new.lsig +${gcmd} clerk multisig signprogram --legacy-msig=false -L ${TEMPDIR}/mtrue_new.lsig -a ${ACCOUNTB} -o ${TEMPDIR}/mtrue_new2.lsig +${gcmd} clerk send --amount 100000 --from ${ACCOUNTM} --to ${ACCOUNTB} -L ${TEMPDIR}/mtrue_new2.lsig + +# Test that mixing modes fails: since this is vFuture, mtrue.lsig has LMsig field +# Try to use it with --legacy-msig=true (which expects Msig field) +set +e +OUTPUT=$(${gcmd} clerk multisig signprogram --legacy-msig=true -L ${TEMPDIR}/mtrue.lsig -a ${ACCOUNTB} -o ${TEMPDIR}/mtrue_mixed.lsig 2>&1) +if [ $? -eq 0 ]; then + echo "ERROR: Expected failure when mixing new signature with legacy mode, but command succeeded" + exit 1 +fi +echo "$OUTPUT" | grep -q "LogicSig file contains LMsig field" +if [ $? -ne 0 ]; then + echo "ERROR: Expected error message about LMsig field, got: $OUTPUT" + exit 1 +fi +echo "Correctly rejected mixing new signature with legacy mode" +set -e + echo "#pragma version 1" | ${gcmd} clerk compile - echo "#pragma version 2" | ${gcmd} clerk compile - diff --git a/test/scripts/e2e_subs/v32/e2e-teal-multisig.sh b/test/scripts/e2e_subs/v32/e2e-teal-multisig.sh new file mode 100755 index 0000000000..ae7dd4b1f4 --- /dev/null +++ b/test/scripts/e2e_subs/v32/e2e-teal-multisig.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# Test multisig logic signatures on old algod (v32 consensus) +# v32 consensus should have LogicSigMsig=true, LogicSigLMsig=false (legacy mode only) + +date '+e2e_teal_multisig_v32 start %Y%m%d_%H%M%S' + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +gcmd="goal -w ${WALLET}" + +# Create test accounts +ACCOUNT_A=$(${gcmd} account list|awk '{ print $3 }') +ACCOUNT_B=$(${gcmd} account new|awk '{ print $6 }') +ACCOUNT_C=$(${gcmd} account new|awk '{ print $6 }') + +# Create a 2-of-3 multisig account +ACCOUNT_MSIG=$(${gcmd} account multisig new -T 2 ${ACCOUNT_A} ${ACCOUNT_B} ${ACCOUNT_C}|awk '{ print $6 }') + +# Create a simple always-true program +cat >${TEMPDIR}/msig_true.teal<&1 | tee ${TEMPDIR}/new_send.out +SEND_RESULT=$? +set -e + +if [ $SEND_RESULT -eq 0 ] || ! grep -q "LMsig field not supported" ${TEMPDIR}/new_send.out; then + echo "ERROR: Expected failure with 'LMsig field not supported' error but got:" + cat ${TEMPDIR}/new_send.out + exit 1 +fi +echo "New mode transaction rejected on v32" + +# Auto-detection should use legacy mode on v32 +${gcmd} clerk multisig signprogram -p ${TEMPDIR}/msig_true.teal -a ${ACCOUNT_A} -A ${ACCOUNT_MSIG} -o ${TEMPDIR}/auto1.lsig +${gcmd} clerk multisig signprogram -L ${TEMPDIR}/auto1.lsig -a ${ACCOUNT_B} -o ${TEMPDIR}/auto2.lsig +${gcmd} clerk send --amount 100000 --from ${ACCOUNT_MSIG} --to ${ACCOUNT_A} -L ${TEMPDIR}/auto2.lsig +if [ $? -ne 0 ]; then + echo "ERROR: Auto-detection transaction failed on v32" + exit 1 +fi +echo "Auto-detection correctly used legacy mode on v32" +if ! cat ${TEMPDIR}/auto2.lsig | msgpacktool -d | grep -q '"msig"'; then + echo "ERROR: Auto-detection did not use legacy mode (Msig field not found)" + exit 1 +fi +echo "Auto-detection used legacy mode (Msig field present)" + +# Error cases +set +e +OUTPUT=$(${gcmd} clerk multisig signprogram --legacy-msig=false -L ${TEMPDIR}/legacy1.lsig -a ${ACCOUNT_C} -o ${TEMPDIR}/mixed.lsig 2>&1) +if [ $? -eq 0 ] || ! echo "$OUTPUT" | grep -q "contains Msig field"; then + echo "ERROR: Expected failure with 'contains Msig field' error but got:" + echo "$OUTPUT" + exit 1 +fi +echo "Correctly rejected mixing legacy signature with new mode" + +OUTPUT2=$(${gcmd} clerk multisig signprogram --legacy-msig=true -L ${TEMPDIR}/new1.lsig -a ${ACCOUNT_C} -o ${TEMPDIR}/mixed2.lsig 2>&1) +if [ $? -eq 0 ] || ! echo "$OUTPUT2" | grep -q "contains LMsig field"; then + echo "ERROR: Expected failure with 'contains LMsig field' error but got:" + echo "$OUTPUT2" + exit 1 +fi +echo "Correctly rejected mixing new signature with legacy mode" +set -e + +# When algod is offline, auto-detection should use new mode +goal node stop +sleep 2 + +# Try auto-detection while offline - should fail and default to new mode (LMsig) +set +e +${gcmd} clerk multisig signprogram -p ${TEMPDIR}/msig_true.teal -a ${ACCOUNT_A} -A ${ACCOUNT_MSIG} -o ${TEMPDIR}/offline_auto.lsig 2>&1 | tee ${TEMPDIR}/offline_test.out +OFFLINE_RESULT=$? +if [ $OFFLINE_RESULT -ne 0 ]; then + echo "ERROR: Failed to create signature while algod offline" + exit 1 +fi +echo "Created signature while algod offline" + +# Use msgpacktool to verify which field was used +if msgpacktool -d < ${TEMPDIR}/offline_auto.lsig | grep -q '"lmsig"'; then + echo "Auto-detection defaulted to new mode (LMsig) when offline" +elif msgpacktool -d < ${TEMPDIR}/offline_auto.lsig | grep -q '"msig"'; then + echo "ERROR: Auto-detection used legacy mode (Msig) when offline - expected new mode" + exit 1 +else + echo "ERROR: Unable to determine which field was used in offline signature" + msgpacktool -d < ${TEMPDIR}/offline_auto.lsig + exit 1 +fi +set -e + +date '+e2e_teal_multisig_v32 done %Y%m%d_%H%M%S' From 286955500814d800d6a073eddd23df9daa5173a3 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 25 Aug 2025 21:43:08 -0400 Subject: [PATCH 21/25] tests: increase wait timeout in TestTotalWeightChanges (#6420) --- test/e2e-go/features/stateproofs/stateproofs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index db770285c9..a83113afe4 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -847,7 +847,7 @@ func TestTotalWeightChanges(t *testing.T) { if testing.Short() { a.NoError(fixture.WaitForRound(rnd, 30*time.Second)) } else { - a.NoError(fixture.WaitForRound(rnd, 60*time.Second)) + a.NoError(fixture.WaitForRound(rnd, 120*time.Second)) } blk, err := libgoal.BookkeepingBlock(rnd) a.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) From f293437a12ec311293114e33da63be4a636a29bd Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:27:20 -0400 Subject: [PATCH 22/25] tests: fix TestStateProofLogging (#6421) --- data/pools/transactionPool_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index e3bf0a378f..475cb27e94 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -1459,6 +1459,9 @@ func TestStateProofLogging(t *testing.T) { phdr, err := mockLedger.BlockHdr(0) require.NoError(t, err) b.BlockHeader.Branch = phdr.Hash() + if proto.EnableSha512BlockHash { + b.BlockHeader.Branch512 = phdr.Hash512() + } _, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000, nil) require.NoError(t, err) @@ -1479,6 +1482,9 @@ func TestStateProofLogging(t *testing.T) { phdr, err := mockLedger.BlockHdr(basics.Round(i)) require.NoError(t, err) b.BlockHeader.Branch = phdr.Hash() + if proto.EnableSha512BlockHash { + b.BlockHeader.Branch512 = phdr.Hash512() + } b.BlockHeader.TimeStamp = phdr.TimeStamp + 10 if i == 513 { From efcac3ff7e420c15c045b9450df0ff02f3c9fc2e Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:37:45 -0400 Subject: [PATCH 23/25] CI: remove t.Parallel() from TestAbsentTracking (#6415) --- ledger/eval_simple_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index a60311f127..dfe051b578 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -405,7 +405,6 @@ func TestIncentiveEligible(t *testing.T) { // properly. func TestAbsentTracking(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis(func(cfg *ledgertesting.GenesisCfg) { cfg.OnlineCount = 2 // So we know proposer should propose every 2 rounds, on average From 26d04e5c39b5202d23435a59ebca00f889e0b3e5 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:38:31 -0400 Subject: [PATCH 24/25] Consensus: Consensus version v41 upgrade. (#6422) Co-authored-by: cce <51567+cce@users.noreply.github.com> --- config/consensus.go | 41 +++++++++++++++-------- data/transactions/logic/langspec_v1.json | 2 +- data/transactions/logic/langspec_v10.json | 2 +- data/transactions/logic/langspec_v11.json | 2 +- data/transactions/logic/langspec_v12.json | 2 +- data/transactions/logic/langspec_v2.json | 2 +- data/transactions/logic/langspec_v3.json | 2 +- data/transactions/logic/langspec_v4.json | 2 +- data/transactions/logic/langspec_v5.json | 2 +- data/transactions/logic/langspec_v6.json | 2 +- data/transactions/logic/langspec_v7.json | 2 +- data/transactions/logic/langspec_v8.json | 2 +- data/transactions/logic/langspec_v9.json | 2 +- data/transactions/verify/txn_test.go | 8 ++--- ledger/testing/consensusRange.go | 1 + protocol/consensus.go | 7 +++- 16 files changed, 50 insertions(+), 31 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index 3e5f09ffea..36daad899b 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1435,24 +1435,37 @@ func initConsensusProtocols() { // our current max is 250000 v39.ApprovedUpgrades[protocol.ConsensusV40] = 208000 - // ConsensusFuture is used to test features that are implemented - // but not yet released in a production protocol version. - vFuture := v40 - vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + v41 := v40 + v41.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + + v41.LogicSigVersion = 12 - vFuture.LogicSigVersion = 12 // When moving this to a release, put a new higher LogicSigVersion here - vFuture.EnableAppVersioning = true // if not promoted when v12 goes into effect, update logic/field.go - vFuture.EnableSha512BlockHash = true + v41.EnableAppVersioning = true + v41.EnableSha512BlockHash = true - vFuture.EnableUnnamedBoxAccessInNewApps = true + v41.EnableUnnamedBoxAccessInNewApps = true // txn.Access work - vFuture.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same - vFuture.MaxAppAccess = 16 // Twice as many, though cross products are explicit - vFuture.BytesPerBoxReference = 2048 // Count is more important that bytes, loosen up - vFuture.EnableInnerClawbackWithoutSenderHolding = true - vFuture.LogicSigMsig = false - vFuture.LogicSigLMsig = true + v41.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same + v41.MaxAppAccess = 16 // Twice as many, though cross products are explicit + v41.BytesPerBoxReference = 2048 // Count is more important that bytes, loosen up + v41.EnableInnerClawbackWithoutSenderHolding = true + v41.LogicSigMsig = false + v41.LogicSigLMsig = true + + Consensus[protocol.ConsensusV41] = v41 + + // v40 can be upgraded to v41, with an update delay of 7d: + // 208000 = (7 * 24 * 60 * 60 / 2.9 ballpark round times) + // our current max is 250000 + v40.ApprovedUpgrades[protocol.ConsensusV41] = 208000 + + // ConsensusFuture is used to test features that are implemented + // but not yet released in a production protocol version. + vFuture := v41 + vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + + vFuture.LogicSigVersion = 13 // When moving this to a release, put a new higher LogicSigVersion here Consensus[protocol.ConsensusFuture] = vFuture diff --git a/data/transactions/logic/langspec_v1.json b/data/transactions/logic/langspec_v1.json index 10ff7909f7..cca94aaf01 100644 --- a/data/transactions/logic/langspec_v1.json +++ b/data/transactions/logic/langspec_v1.json @@ -1,6 +1,6 @@ { "Version": 1, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index 51a83e7250..3257cfca4b 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -1,6 +1,6 @@ { "Version": 10, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v11.json b/data/transactions/logic/langspec_v11.json index 970ff01702..9fb5a995ba 100644 --- a/data/transactions/logic/langspec_v11.json +++ b/data/transactions/logic/langspec_v11.json @@ -1,6 +1,6 @@ { "Version": 11, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v12.json b/data/transactions/logic/langspec_v12.json index 1e2ebeea2a..8c6541e493 100644 --- a/data/transactions/logic/langspec_v12.json +++ b/data/transactions/logic/langspec_v12.json @@ -1,6 +1,6 @@ { "Version": 12, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v2.json b/data/transactions/logic/langspec_v2.json index 584339b88a..6ad55bb1ca 100644 --- a/data/transactions/logic/langspec_v2.json +++ b/data/transactions/logic/langspec_v2.json @@ -1,6 +1,6 @@ { "Version": 2, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v3.json b/data/transactions/logic/langspec_v3.json index 8ed4c5f45c..6193add93c 100644 --- a/data/transactions/logic/langspec_v3.json +++ b/data/transactions/logic/langspec_v3.json @@ -1,6 +1,6 @@ { "Version": 3, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v4.json b/data/transactions/logic/langspec_v4.json index 988f628246..06b7826eeb 100644 --- a/data/transactions/logic/langspec_v4.json +++ b/data/transactions/logic/langspec_v4.json @@ -1,6 +1,6 @@ { "Version": 4, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v5.json b/data/transactions/logic/langspec_v5.json index 5a7cbb6532..07fd2773c6 100644 --- a/data/transactions/logic/langspec_v5.json +++ b/data/transactions/logic/langspec_v5.json @@ -1,6 +1,6 @@ { "Version": 5, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v6.json b/data/transactions/logic/langspec_v6.json index 09e2476a06..962063ccf3 100644 --- a/data/transactions/logic/langspec_v6.json +++ b/data/transactions/logic/langspec_v6.json @@ -1,6 +1,6 @@ { "Version": 6, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json index f0148b970f..22a1c166ea 100644 --- a/data/transactions/logic/langspec_v7.json +++ b/data/transactions/logic/langspec_v7.json @@ -1,6 +1,6 @@ { "Version": 7, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json index fddcc13a31..196db5f565 100644 --- a/data/transactions/logic/langspec_v8.json +++ b/data/transactions/logic/langspec_v8.json @@ -1,6 +1,6 @@ { "Version": 8, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json index 28f3e9f995..5ac362e3c4 100644 --- a/data/transactions/logic/langspec_v9.json +++ b/data/transactions/logic/langspec_v9.json @@ -1,6 +1,6 @@ { "Version": 9, - "LogicSigVersion": 11, + "LogicSigVersion": 12, "NamedTypes": [ { "Name": "[]byte", diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 55665cdae6..12d853af5b 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -1058,7 +1058,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= signedTxn[i].Txn.Sender = multiAddress[s] signedTxn[i].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} signedTxn[i].Lsig.Logic = op.Program - program := logic.Program(op.Program) + program := logic.MultisigProgram{Addr: crypto.Digest(multiAddress[s]), Program: op.Program} // create multi sig that 2 out of 3 has signed the txn var sigs [2]crypto.MultisigSig @@ -1069,7 +1069,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= } msig, err := crypto.MultisigAssemble(sigs[:]) require.NoError(t, err) - signedTxn[i].Lsig.Msig = msig + signedTxn[i].Lsig.LMsig = msig } txnGroups := make([][]transactions.SignedTxn, len(signedTxn)) @@ -1079,10 +1079,10 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= } breakSignatureFunc := func(txn *transactions.SignedTxn) { - txn.Lsig.Msig.Subsigs[0].Sig[0]++ + txn.Lsig.LMsig.Subsigs[0].Sig[0]++ } restoreSignatureFunc := func(txn *transactions.SignedTxn) { - txn.Lsig.Msig.Subsigs[0].Sig[0]-- + txn.Lsig.LMsig.Subsigs[0].Sig[0]-- } verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchHasFailedSigs.Error()) diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 4d15808925..c6d3265cbc 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -62,6 +62,7 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusV38, // AVM v9, ECDSA pre-check, stateproofs recoverability protocol.ConsensusV39, // AVM v10, logicsig opcode budget pooling, elliptic curve ops, dynamic round times protocol.ConsensusV40, // Consensus incentives, AVM v11, mimc + protocol.ConsensusV41, // AVM v12, txn access, Sha512BlockHash, AppVersioning protocol.ConsensusFuture, } diff --git a/protocol/consensus.go b/protocol/consensus.go index e8bcfe1161..981eb37288 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -224,6 +224,11 @@ const ConsensusV40 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/236dcc18c9c507d794813ab768e467ea42d1b4d9", ) +// ConsensusV41 enables txn access, Sha512BlockHash, AppVersioning and TEAL v12 including the falcon verify opcode +const ConsensusV41 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/953304de35264fc3ef91bcd05c123242015eeaed", +) + // ConsensusFuture is a protocol that should not appear in any production // network, but is used to test features before they are released. const ConsensusFuture = ConsensusVersion( @@ -253,7 +258,7 @@ const ConsensusVAlpha5 = ConsensusVersion("alpha5") // ConsensusCurrentVersion is the latest version and should be used // when a specific version is not provided. -const ConsensusCurrentVersion = ConsensusV40 +const ConsensusCurrentVersion = ConsensusV41 // Error is used to indicate that an unsupported protocol has been detected. type Error ConsensusVersion From 8504c6d149010843100651c3e090c22df9ff104d Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Tue, 26 Aug 2025 18:59:42 +0000 Subject: [PATCH 25/25] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +0 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885